ML-DSA이란?
양자컴퓨터 공격에 대비한 격자 기반 전자서명 알고리즘입니다.
서명된 데이터가 변경되지 않았는지, 올바른 개인키로 서명되었는지 검증하는 데 사용합니다.
i 테스트 가이드
[사전안내] 테스트를 수행하려면 PC에 ML-DSA 기반 PQC 인증서와 개인키가 있어야 합니다.
PC에 저장된 인증서를 선택해 전자서명을 생성하고, 서버에서 해당 서명을 검증합니다.
서버 검증에 성공하면 선택한 인증서로 만든 서명이 정상적으로 확인된 것입니다.
ML-KEM이란?
양자컴퓨터 환경에서도 안전하게 사용할 수 있도록 설계된 키 캡슐화 매커니즘입니다.
두 시스템이 같은 비밀값을 안전하게 만들고, 이 값을 이후 데이터 암호화에 활용합니다.
i 테스트 가이드
서버에서 키쌍을 생성한 뒤, 공개키로 캡슐화하고 개인키로 디캡슐화합니다.
캡슐화로 만든 공유 비밀값과 디캡슐화로 복원한 값이 일치하면 테스트 성공입니다.
SLH-DSA이란?
해시 함수 기반의 PQC 전자서명 알고리즘입니다.
서명값이 큰 편이므로, 자주 전송되는 데이터보다는 중요 문서, 코드 서명, 정책 파일처럼 장기 검증이 필요한 환경에 적합합니다.
i 테스트 가이드
서버에 미리 설정된 SLH-DSA 키로 테스트를 진행합니다.
서버가 테스트 데이터에 서명을 생성하고, 대응되는 공개키로 서명을 검증합니다.
서명 생성과 검증이 모두 완료되면 테스트 성공입니다.
FALCON이란?
NTRU 격자 문제를 기반으로 한 PQC 전자서명 알고리즘입니다.(NIST 표준화 명칭 FN-DSA 예정)
서명 크기를 작게 유지하는 것이 특징이며, 서명 데이터 크기가 중요한 환경에서 검토할 수 있습니다.
i 테스트 가이드
서버에 미리 설정된 FALCON 키로 테스트를 진행합니다.
서버가 테스트 데이터에 서명을 생성하고, 해당 공개키로 서명을 검증합니다.
서명 생성과 검증이 모두 완료되면 테스트 성공입니다.
PQC(ML-KEM) 암복호 연산을 위한 CipherUtil 인스턴스를 생성하고 ML-KEM 키쌍을 생성합니다.
| Name | Type | Description |
|---|---|---|
| algorithm | String | PQC 알고리즘명 (ML-KEM-512/768/1024) |
| Name | Type | Description |
|---|---|---|
| publicKey | String | 생성된 ML-KEM 공개키 (Base64) |
// ML-KEM 키쌍 생성 및 CipherUtil 인스턴스 생성
KeyPair keyPair = PQCKeyUtil.generatePQCKeyPair("ML-KEM-1024");
CipherUtil cipher = new CipherUtil("ML-KEM-1024");
result.put("algorithm", "ML-KEM-1024");
result.put("publicKey", Base64Util.encode(keyPair.getPublic().getEncoded()));
수신자의 ML-KEM 공개키로 평문을 암호화합니다.
| Name | Type | Description |
|---|---|---|
| publicKey | PublicKey | 수신자 ML-KEM 공개키 |
| plaintext | byte[] | 암호화할 평문 |
| Name | Type | Description |
|---|---|---|
| packet | byte[] | 암호 패킷 (Base64) |
KeyPair keyPair = PQCKeyUtil.generatePQCKeyPair("ML-KEM-1024");
CipherUtil cipher = new CipherUtil();
byte[] plaintext = "testDATA123!@#".getBytes();
byte[] packet = cipher.encryptMLKEM(keyPair.getPublic(), plaintext);
if (packet == null) {
throw new Exception(cipher.getErrorMsg());
}
result.put("packet", Base64Util.encode(packet));
수신자의 ML-KEM 개인키(PBES2 암호화본)로 암호 패킷을 복호화합니다.
| Name | Type | Description |
|---|---|---|
| encryptedPrivateKey | byte[] | PBES2 암호화된 개인키 |
| algorithm | String | ML-KEM 알고리즘명 |
| password | String | 개인키 패스워드 |
| packet | byte[] | 암호 패킷 |
| Name | Type | Description |
|---|---|---|
| plaintext | String | 복호화된 평문 |
KeyPair keyPair = PQCKeyUtil.generatePQCKeyPair("ML-KEM-1024");
byte[] encPriKey = KeyUtil.getPBES2EncryptedPrivateKey(keyPair.getPrivate(), "signgate1!");
CipherUtil cipher = new CipherUtil();
byte[] packet = cipher.encryptMLKEM(keyPair.getPublic(), "testDATA123!@#".getBytes());
byte[] decrypted = cipher.decryptMLKEM(encPriKey, "ML-KEM-1024", "signgate1!", packet);
if (decrypted == null) {
throw new Exception(cipher.getErrorMsg());
}
result.put("plaintext", new String(decrypted));
수신자 공개키로 대칭키 래핑(wrap) 컨텍스트를 초기화합니다.
| Name | Type | Description |
|---|---|---|
| publicKey | PublicKey | 수신자 ML-KEM 공개키 |
| Name | Type | Description |
|---|---|---|
| wrapInit | boolean | 초기화 성공 여부 |
KeyPair keyPair = PQCKeyUtil.generatePQCKeyPair("ML-KEM-1024");
CipherUtil cipher = new CipherUtil(keyPair.getPublic().getAlgorithm());
boolean init = cipher.wrapInit(keyPair.getPublic());
result.put("wrapInit", init);
대칭키(AES-256 + IV)를 ML-KEM 으로 래핑한 암호화 키를 생성합니다.
| Name | Type | Description |
|---|---|---|
| symmetricKey | byte[] | 래핑할 대칭키 (AES 32 + IV 16) |
| algorithm | String | 대칭키 알고리즘 ("AES") |
| Name | Type | Description |
|---|---|---|
| wrappedKey | byte[] | 래핑된 암호화 키 (Base64) |
CipherUtil cipher = new CipherUtil(publicKey.getAlgorithm());
cipher.wrapInit(publicKey);
// AES-256 키(32) + IV(16) = 48 byte 대칭키
byte[] wrappedKey = cipher.wrapUpdate(symmetricKey, "AES");
cipher.wrapFinal();
result.put("wrappedKey", Base64Util.encode(wrappedKey));
대칭키 래핑 연산을 종료하고 컨텍스트를 정리합니다.
| Name | Type | Description |
|---|---|---|
| - | - | 입력 없음 |
| Name | Type | Description |
|---|---|---|
| wrapFinal | void | 래핑 종료 결과 |
cipher.wrapInit(publicKey);
byte[] wrappedKey = cipher.wrapUpdate(symmetricKey, "AES");
cipher.wrapFinal(); // 래핑 컨텍스트 정리
수신자 개인키로 대칭키 언래핑(unwrap) 컨텍스트를 초기화합니다.
| Name | Type | Description |
|---|---|---|
| privateKey | PrivateKey | 수신자 ML-KEM 개인키 |
| Name | Type | Description |
|---|---|---|
| unwrapInit | boolean | 초기화 성공 여부 |
KeyPair keyPair = PQCKeyUtil.generatePQCKeyPair("ML-KEM-1024");
CipherUtil cipher = new CipherUtil(keyPair.getPublic().getAlgorithm());
boolean init = cipher.unwrapInit(keyPair.getPrivate());
result.put("unwrapInit", init);
랩핑된 암호화 키를 개인키로 복원하여 원본 대칭키를 추출합니다.
| Name | Type | Description |
|---|---|---|
| wrappedKey | byte[] | 래핑된 암호화 키 |
| algorithm | String | 대칭키 알고리즘 ("AES") |
| Name | Type | Description |
|---|---|---|
| unwrappedKey | byte[] | 복원된 대칭키 (Base64) |
| match | boolean | 원본 대칭키와 일치 여부 |
CipherUtil dec = new CipherUtil(algorithm);
dec.unwrapInit(privateKey);
byte[] unwrappedKey = dec.unwrapUpdate(wrappedKey, "AES");
dec.unwrapFinal();
result.put("unwrappedKey", Base64Util.encode(unwrappedKey));
대칭키 언래핑을 종료하고, 복원한 대칭키로 AES 암복호 라운드트립을 검증합니다.
| Name | Type | Description |
|---|---|---|
| - | - | 입력 없음 |
| Name | Type | Description |
|---|---|---|
| aesRoundTrip | String | 복원 대칭키로 복호화한 평문 |
| match | boolean | 원문 일치 여부 |
dec.unwrapInit(privateKey);
byte[] unwrappedKey = dec.unwrapUpdate(wrappedKey, "AES");
dec.unwrapFinal();
// 복원한 대칭키(AES 256 + IV)로 평문 암복호 검증
byte[] aesKey = Arrays.copyOfRange(unwrappedKey, 0, 32);
byte[] iv = Arrays.copyOfRange(unwrappedKey, 32, 48);
AESCipherUtil aes = new AESCipherUtil();
aes.decryptInit(aesKey, iv);
byte[] decrypted = aes.decryptUpdate(encrypted);
aes.decryptFinal();
result.put("aesRoundTrip", new String(decrypted));