Java 연동

Prev Next

Hardware Security Module(HSM) 상품은 Thales Luna HSM을 기반으로 하며, Java Security Provider(LunaProvider)를 통해 표준 JCE(Java Cryptography Extension) API로 사용할 수 있습니다. 이 문서에서는 Java 애플리케이션에서 LunaProvider로 HSM 파티션에 접속해 AES/RSA 같은 기본 알고리즘으로 키 생성, 암호화·복호화, 서명·검증을 수행하는 최소 예제를 안내합니다.

참고

CLI로 먼저 만져보려면 CLI 도구 활용 (lunacm/ckdemo/multitoken)을 참고해 주십시오.
LunaClient를 설치하면 KeyStoreLunaDemo, SignatureECDSADemo 같은 JSP(Java Security Provider) 샘플 코드도 함께 설치됩니다(lunajspsamples 패키지, 보통 /usr/safenet/lunaclient/jsp/samples 아래). 이 문서에서 다루지 않는 사용법이 필요하면 먼저 이 샘플들을 찾아보십시오.

지원 환경

지원 환경을 설명합니다.

전제 조건

연동을 시작하기 전, 다음의 전제 조건을 준수해 주십시오.

  • HSM 클라이언트 서버에 Java가 설치 및 구동되고 있음
  • HSM 클라이언트 서버에 Luna HSM Client(LunaClient) 10.9.3이 설치되어 있음 — LunaClient 설치 가이드
  • HSM 클라이언트 서버 인스턴스와 HSM 파티션 간에 연결 생성 완료
  • HSM 파티션 초기 설정 완료

지원 버전

구성 요소 버전 비고
HSM 클라이언트(Luna Client) 10.9.3 LunaClient 설치 가이드
JDK 8 이상 (이 가이드는 17 기준으로 작성) 이 문서의 예제는 표준 JCE API(KeyGenerator, KeyPairGenerator, Cipher, Signature)만 사용하므로 JDK 8 이상이면 동작합니다. 다만 이 문서의 컴파일·실행 명령과 출력 결과는 JDK 17 기준으로 작성 및 확인했습니다.

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로 확인할 수 있습니다. (CLI 도구 활용 참고)

javac/java로 컴파일·실행하기

# 1. 컴파일 — LunaProvider.jar를 클래스패스에 추가
javac -cp /usr/safenet/lunaclient/jsp/lib/LunaProvider.jar KeyGenExample.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/ \
     KeyGenExample

다른 예제도 클래스 이름만 바꾸면 동일합니다.

키 생성

KeyGenExample.java 파일을 아래 내용으로 생성하십시오. AES 대칭키와 RSA 키쌍을 모두 HSM 파티션 내부에서 생성하며, 개인키/비밀키는 파티션 밖으로 반출되지 않습니다.

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 javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.safenetinc.luna.provider.LunaProvider;

public class KeyGenExample {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    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());

        // AES-256 대칭키 생성
        KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES", lunaProvider);
        aesKeyGen.init(256);
        SecretKey aesKey = aesKeyGen.generateKey();

        // RSA-2048 키쌍 생성
        KeyPairGenerator rsaKeyGen = KeyPairGenerator.getInstance("RSA", lunaProvider);
        rsaKeyGen.initialize(2048);
        KeyPair rsaKeyPair = rsaKeyGen.generateKeyPair();

        System.out.println("AES key algorithm: " + aesKey.getAlgorithm());
        System.out.println("RSA public key algorithm: " + rsaKeyPair.getPublic().getAlgorithm());
    }
}

컴파일·실행 후 다음과 같은 결과가 출력되면 정상입니다.

AES key algorithm: AES
RSA public key algorithm: RSA

생성한 키를 다음 실행에서도 다시 쓰려면

위 예제처럼 KeyGenerator/KeyPairGenerator로 생성만 한 키는 세션(session) 객체로 만들어집니다. 즉 프로그램(JVM 세션)이 종료되면 HSM에서도 함께 사라지므로, 다음번 실행에서 같은 키를 다시 쓸 수 없습니다.

같은 키를 다음 실행에서도 찾아 쓰려면, alias(이름)를 붙여 KeyStore에 저장해야 합니다. 파일 기반 KeyStore(JKS 등)와 달리 Luna의 KeyStoresetKeyEntry/setEntry 호출 즉시 HSM에 영구(token) 객체로 기록되며, alias는 그대로 해당 객체의 PKCS#11 CKA_LABEL이 됩니다. 별도로 store()를 호출할 필요는 없습니다.

// 저장 (alias: "my-aes-key")
luna.setKeyEntry("my-aes-key", aesKey, null, null);

// 다음 실행(또는 다른 프로세스)에서 재사용 — 같은 파티션에 로그인한 뒤
SecretKey reloadedKey = (SecretKey) luna.getKey("my-aes-key", null);
참고

setKeyEntry(alias, key, password, chain)chain 인자는 PrivateKey를 저장할 때만 요구되는 표준 JCA KeyStore API 제약입니다. AES 같은 SecretKeychainnull로 두면 되지만, RSA 개인키를 영구 저장하려면 인증서 체인이 있어야 합니다. SignatureECDSADemo, KeyStoreLunaDemo 같은 JSP 샘플 코드를 참고해 주십시오.

AES로 암호화·복호화 수행

EncryptExample.java 파일을 아래 내용으로 생성하십시오.

import java.io.ByteArrayInputStream;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import com.safenetinc.luna.provider.LunaProvider;

public class EncryptExample {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    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());

        KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES", lunaProvider);
        aesKeyGen.init(256);
        SecretKey aesKey = aesKeyGen.generateKey();

        byte[] plaintext = "HSM Java Quickstart test data".getBytes("UTF-8");

        // 암호화
        Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", lunaProvider);
        encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);
        byte[] ciphertext = encryptCipher.doFinal(plaintext);
        IvParameterSpec iv = new IvParameterSpec(encryptCipher.getIV());

        // 복호화
        Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", lunaProvider);
        decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, iv);
        byte[] decrypted = decryptCipher.doFinal(ciphertext);

        System.out.println("Decryption matches original: " + Arrays.equals(plaintext, decrypted));
    }
}

컴파일·실행 후 다음과 같은 결과가 출력되면 정상입니다.

Decryption matches original: true

RSA로 전자서명 생성 및 검증

SignatureExample.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 SignatureExample {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    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 rsaKeyGen = KeyPairGenerator.getInstance("RSA", lunaProvider);
        rsaKeyGen.initialize(2048);
        KeyPair rsaKeyPair = rsaKeyGen.generateKeyPair();

        byte[] message = "HSM Java Quickstart signature test".getBytes("UTF-8");

        // 서명
        Signature signer = Signature.getInstance("SHA256withRSA", lunaProvider);
        signer.initSign(rsaKeyPair.getPrivate());
        signer.update(message);
        byte[] signature = signer.sign();

        // 검증
        Signature verifier = Signature.getInstance("SHA256withRSA", lunaProvider);
        verifier.initVerify(rsaKeyPair.getPublic());
        verifier.update(message);
        boolean verified = verifier.verify(signature);

        System.out.println("Signature verification result: " + verified);
    }
}

컴파일·실행 후 다음과 같은 결과가 출력되면 정상입니다.

Signature verification result: true

문제 해결

로그인 실패

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)이나 파티션 초기 설정이 완료되지 않은 경우 발생할 수 있습니다.

해결 방법

전제 조건의 HSM 커넥션 생성·파티션 초기 설정이 완료됐는지 확인하십시오.

다음 단계