--- title: "양자내성암호(PQC) Java 예제" slug: "hsm-vpc-pqc-java" tags: ["Hardware Security Module"] updated: 2026-10-22T12:38:41Z published: 2026-10-22T12:38:41Z canonical: "guide-gov.ncloud-docs.com/hsm-vpc-pqc-java" --- > ## Documentation Index > Fetch the complete documentation index at: https://guide-gov.ncloud-docs.com/llms.txt > Use this file to discover all available pages before exploring further. # 양자내성암호(PQC) Java 예제 Hardware Security Module(HSM) 서비스는 Thales Luna HSM을 기반으로 하며, NIST 표준 양자내성암호(PQC) 알고리즘인 ML-KEM (FIPS 203, 키 캡슐화)과 ML-DSA (FIPS 204, 전자서명)를 지원합니다. Java Security Provider (LunaProvider)가 이를 표준 JCE (Java Cryptography Extension) API로 노출하므로, 별도의 네이티브 연동 코드 없이 표준 Java API로 사용할 수 있습니다. 이 문서에서는 Java 애플리케이션에서 LunaProvider를 통해 PQC 키를 생성하고, 키 캡슐화(KEM) 및 전자서명을 수행하는 방법을 안내합니다. 참고 이 문서는 ML-KEM/ML-DSA만 다룹니다. HSS/LMS는 [C++ 예제 가이드](/docs/hsm-vpc-pqc-cpp)를, 전체 PQC 기능 목록은 [양자내성암호(PQC) 지원 기능](/docs/hsm-pqc-overview)를 참조해 주십시오. ## 지원 환경 예제를 실행하기 전에 확인할 전제 조건과 지원 버전을 안내합니다. ### 전제 조건 연동을 시작하기 전, 다음의 전제 조건을 확인해 주십시오. - HSM 클라이언트 서버에 Java 설치 - HSM 클라이언트 서버에 Luna HSM Client(LunaClient) 10.9.3 설치([LunaClient 설치 가이드](/docs/hsm-client) 참조) - HSM 클라이언트 서버 인스턴스와 HSM 파티션 간에 연결 생성 완료 - HSM 파티션 초기 설정 완료 ### 지원 버전 예제를 실행하는 데 필요한 구성 요소의 지원 버전은 다음과 같습니다. | 구성 요소 | 버전 | 비고 | | --- | --- | --- | | HSM 클라이언트(Luna Client) | 10.9.3 | [LunaClient 설치 가이드](/docs/hsm-client) | | JDK | OpenJDK 또는 Oracle JDK 24 이상 (LTS는 25 이상) | [Luna HSM Client 10.9.1 Customer Release Notes](https://www.thalesdocs.com/gphsm/luna/7/docs/network/Content/CRN/Luna/client/10-9-1.htm)의 "Java Lunaprovider Support for ML-KEM and ML-DSA" 섹션에 명시된 지원 버전 | ## Luna HSM을 위한 Java 설정 Java 애플리케이션에서 LunaProvider를 사용하려면 `java.security` 설정 파일에 LunaProvider를 등록해야 합니다. ``` security.provider.1=SUN security.provider.2=com.safenetinc.luna.provider.LunaProvider security.provider.3=SunRsaSign security.provider.4=SunEC security.provider.5=SunJSSE security.provider.6=SunJCE security.provider.7=SunJGSS security.provider.8=SunSASL security.provider.9=XMLDSig security.provider.10=SunPCSC security.provider.11=JdkLDAP security.provider.12=JdkSASL security.provider.13=SunPKCS11 ``` 또는 애플리케이션 코드에서 직접 Provider를 추가해도 됩니다. 아래 예제들은 모두 이 방식을 사용합니다. ``` Provider lunaProvider = new LunaProvider(); Security.addProvider(lunaProvider); ``` ### 파티션 로그인 Provider를 추가한 것만으로는 HSM 파티션에 로그인되지 않습니다. 아래처럼 `KeyStore`를 로드하는 것이 곧 로그인 절차이며, 아래 모든 예제 코드에 포함되어 있습니다. ``` KeyStore luna = KeyStore.getInstance("Luna"); luna.load(new ByteArrayInputStream(("slot:" + SLOT).getBytes()), PARTITION_PASSWORD.toCharArray()); ``` 슬롯 번호는 `lunacm:> slot list`로 확인할 수 있습니다. ### 컴파일 및 실행 `javac`/`java`로 컴파일/실행해 주십시오. ``` # 1. 컴파일 — LunaProvider.jar를 클래스패스에 추가 javac -cp /usr/safenet/lunaclient/jsp/lib/LunaProvider.jar PqcKeyGenExample.java # 2. 실행 — 컴파일된 클래스(.)와 LunaProvider.jar를 classpath에 넣고, # -Djava.library.path는 java 옵션으로 직접 지정 java -cp .:/usr/safenet/lunaclient/jsp/lib/LunaProvider.jar \ -Djava.library.path=/usr/safenet/lunaclient/jsp/lib/ \ PqcKeyGenExample ``` 다른 예제도 클래스 이름만 바꾸면 동일합니다. ## PQC 키 생성 LunaProvider는 ML-KEM/ML-DSA를 각 파라미터 세트별 알고리즘 이름으로 등록합니다. | 알고리즘 | 등록된 이름(algorithm name) | | --- | --- | | ML-KEM (키 캡슐화) | `ML-KEM`, `ML-KEM-512`, `ML-KEM-768`, `ML-KEM-1024` | | ML-DSA (전자서명) | `ML-DSA`, `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87` | `PqcKeyGenExample.java` 파일을 아래 내용으로 생성해 주십시오. ``` import java.io.ByteArrayInputStream; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.Provider; import java.security.Security; import com.safenetinc.luna.provider.LunaProvider; public class PqcKeyGenExample { // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오. private static final int SLOT = 0; private static final String PARTITION_PASSWORD = "{partition-password}"; public static void main(String[] args) throws Exception { Provider lunaProvider = new LunaProvider(); Security.addProvider(lunaProvider); // 파티션 로그인 (KeyStore 로드 = 로그인) KeyStore luna = KeyStore.getInstance("Luna"); luna.load(new ByteArrayInputStream(("slot:" + SLOT).getBytes()), PARTITION_PASSWORD.toCharArray()); // ML-KEM-768 키 쌍 생성 (HSM 파티션 내부에서 생성되며 개인키는 반출되지 않음) KeyPairGenerator kemKeyGen = KeyPairGenerator.getInstance("ML-KEM-768", lunaProvider); KeyPair kemKeyPair = kemKeyGen.generateKeyPair(); // ML-DSA-65 키 쌍 생성 KeyPairGenerator dsaKeyGen = KeyPairGenerator.getInstance("ML-DSA-65", lunaProvider); KeyPair dsaKeyPair = dsaKeyGen.generateKeyPair(); System.out.println("ML-KEM public key algorithm: " + kemKeyPair.getPublic().getAlgorithm()); System.out.println("ML-DSA public key algorithm: " + dsaKeyPair.getPublic().getAlgorithm()); } } ``` [컴파일 및 실행](/docs/hsm-vpc-pqc-java#%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%B0%8F%EC%8B%A4%ED%96%89) 후 다음과 같은 결과가 출력되는지 확인해 주십시오. ``` ML-KEM public key algorithm: ML-KEM-768 ML-DSA public key algorithm: ML-DSA-65 ``` ## ML-KEM으로 키 캡슐화(KEM) 수행 ML-KEM은 `javax.crypto.KEM` API로 사용합니다 (`java.security.KEM`이 아닙니다). `PqcKemExample.java` 파일을 아래 내용으로 생성해 주십시오. ``` import java.io.ByteArrayInputStream; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.Provider; import java.security.Security; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.KEM; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import com.safenetinc.luna.provider.LunaProvider; public class PqcKemExample { // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오. private static final int SLOT = 0; private static final String PARTITION_PASSWORD = "{partition-password}"; public static void main(String[] args) throws Exception { Provider lunaProvider = new LunaProvider(); Security.addProvider(lunaProvider); // 파티션 로그인 KeyStore luna = KeyStore.getInstance("Luna"); luna.load(new ByteArrayInputStream(("slot:" + SLOT).getBytes()), PARTITION_PASSWORD.toCharArray()); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", lunaProvider); KeyPair keyPair = keyGen.generateKeyPair(); KEM kem = KEM.getInstance("ML-KEM", lunaProvider); // 송신측: 공개키로 공유 비밀을 생성하고 캡슐화(encapsulate) // encapsulate(from, to, algorithm)로 공유 비밀의 바이트 범위와 SecretKey 알고리즘을 지정 KEM.Encapsulator encapsulator = kem.newEncapsulator(keyPair.getPublic()); KEM.Encapsulated encapsulated = encapsulator.encapsulate(0, 32, "AES"); SecretKey senderSecret = encapsulated.key(); byte[] encapsulation = encapsulated.encapsulation(); // 수신측: 개인키와 캡슐(encapsulation)로 동일한 공유 비밀을 복원(decapsulate) KEM.Decapsulator decapsulator = kem.newDecapsulator(keyPair.getPrivate()); SecretKey receiverSecret = decapsulator.decapsulate(encapsulation, 0, 32, "AES"); // HSM에 보관된 SecretKey는 non-extractable이라 getEncoded()로 직접 비교할 수 없으므로, // 한쪽 키로 암호화하고 다른 쪽 키로 복호화해서 원문이 복원되는지로 공유 비밀 일치 여부를 검증 byte[] plaintext = "PQC KEM test data".getBytes("UTF-8"); Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", lunaProvider); encryptCipher.init(Cipher.ENCRYPT_MODE, senderSecret); byte[] ciphertext = encryptCipher.doFinal(plaintext); IvParameterSpec iv = new IvParameterSpec(encryptCipher.getIV()); Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", lunaProvider); decryptCipher.init(Cipher.DECRYPT_MODE, receiverSecret, iv); byte[] decrypted = decryptCipher.doFinal(ciphertext); System.out.println("Shared secret matches: " + Arrays.equals(plaintext, decrypted)); } } ``` [컴파일 및 실행](/docs/hsm-vpc-pqc-java#%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%B0%8F%EC%8B%A4%ED%96%89) 후 다음과 같은 결과가 출력되는지 확인해 주십시오. ``` Shared secret matches: true ``` ## ML-DSA로 전자서명 생성 및 검증 `PqcSignatureExample.java` 파일을 아래 내용으로 생성하십시오. ``` import java.io.ByteArrayInputStream; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.Provider; import java.security.Security; import java.security.Signature; import com.safenetinc.luna.provider.LunaProvider; public class PqcSignatureExample { // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오. private static final int SLOT = 0; private static final String PARTITION_PASSWORD = "{partition-password}"; public static void main(String[] args) throws Exception { Provider lunaProvider = new LunaProvider(); Security.addProvider(lunaProvider); // 파티션 로그인 KeyStore luna = KeyStore.getInstance("Luna"); luna.load(new ByteArrayInputStream(("slot:" + SLOT).getBytes()), PARTITION_PASSWORD.toCharArray()); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", lunaProvider); KeyPair keyPair = keyGen.generateKeyPair(); byte[] message = "PQC signature test".getBytes("UTF-8"); // 서명 Signature signer = Signature.getInstance("ML-DSA", lunaProvider); signer.initSign(keyPair.getPrivate()); signer.update(message); byte[] signature = signer.sign(); // 검증 Signature verifier = Signature.getInstance("ML-DSA", lunaProvider); verifier.initVerify(keyPair.getPublic()); verifier.update(message); boolean verified = verifier.verify(signature); System.out.println("Signature verification result: " + verified); } } ``` [컴파일 및 실행](/docs/hsm-vpc-pqc-java#%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%B0%8F%EC%8B%A4%ED%96%89) 후 다음과 같은 결과가 출력되면 정상입니다. ``` Signature verification result: true ``` ## 문제 해결 Java 예제를 실행할 때 발생할 수 있는 문제와 해결 방법을 안내합니다. ### 로그인 실패 `com.safenetinc.luna.exception.LunaException: No logged in tokens available` 가 발생합니다. #### 원인 `SLOT`/`PARTITION_PASSWORD`를 실제 값으로 바꾸지 않았거나, 슬롯 번호나 비밀번호가 틀린 경우 발생합니다. #### 해결 방법 `lunacm:> slot list`로 슬롯 번호를, 파티션 비밀번호를 다시 확인해 주십시오. ### LunaProvider 로드 실패 `ClassNotFoundException: com.safenetinc.luna.provider.LunaProvider` 또는 `UnsatisfiedLinkError`가 발생합니다. #### 원인 LunaClient가 설치되어 있지 않거나, `javac`/`java` 명령의 `-cp`/`-Djava.library.path` 경로가 실제 설치 경로(`/usr/safenet/lunaclient/jsp/lib/`)와 다른 경우 발생합니다. #### 해결 방법 LunaClient 설치 여부와 경로를 확인해 주십시오. ### 연결 오류 파티션 로그인은 되는데 키 생성/서명 호출에서 응답이 없거나 연결 오류가 납니다. #### 원인 HSM 클라이언트 서버와 HSM 파티션 간의 네트워크 연결(Connection)이나 파티션 초기 설정이 완료되지 않은 경우 발생할 수 있습니다. #### 해결 방법 [전제 조건](/docs/hsm-vpc-pqc-java#%EC%A0%84%EC%A0%9C%EC%A1%B0%EA%B1%B4)의 HSM 커넥션 생성·파티션 초기 설정이 완료됐는지 확인해 주십시오.