BYOK(키 주입) CLI 예제

Prev Next

VPC 환경에서 이용 가능합니다.

이 문서는 보유하고 계신 AES-256 대칭키, RSA/EC 개인키와 공개키를 코드 작성 없이 CLI 도구(cmu, ckdemo, OpenSSL)만으로 NCP Hardware Security Module(HSM) 파티션에 주입(BYOK)하는 절차를 안내합니다. 주입 원리와 지원 범위는 BYOK(키 주입) 개요에서 먼저 확인해 주십시오.

:::(info) (참고)
이 문서는 위에서부터 순서대로 실행하면 전 과정이 완료되도록 구성했습니다.

  1. 실행 공통값 — 슬롯, 비밀번호, 파일 이름 타임스탬프 설정
  2. 샘플 키 준비 — 주입해 볼 키 생성 (보유한 키가 있으면 생략)
  3. 1단계 — wrapping 키쌍 생성과 공개키 반출
  4. ckdemo 준비 — unwrap에 사용하는 ckdemo의 공통 설정
  5. AES-256 대칭키 주입
  6. RSA 키 주입 / EC 키 주입
  7. 주입한 키 조회 / 정리

특정 키 유형만 필요하시면 1~4를 수행한 뒤 해당 절로 건너뛰셔도 됩니다. 공개키만 주입한다면 wrapping 키쌍과 ckdemo가 필요 없으므로 1을 수행한 뒤 RSA 공개키 주입 또는 EC 키 주입의 공개키 부분으로 건너뛰십시오. 같은 절차를 Java 코드로 수행하려면 BYOK(키 주입) Java 예제를 참고해 주십시오.

lunacm과 ckdemo의 기본 사용법(세션 열기, 로그인, 메뉴 구성)은 HSM CLI 도구 사용 가이드에서 안내합니다. cmu(Certificate Management Utility)는 LunaClient와 함께 /usr/safenet/lunaclient/bin/cmu에 설치되며, cmu <명령> -<옵션>=<값> ... 형식의 한 줄 명령으로 키 생성·조회·속성 변경·삭제를 수행합니다. 명령별 옵션은 cmu help -cmd <명령>으로 확인할 수 있습니다. 이 문서에서는 BYOK에 필요한 명령만 다룹니다.

이 문서의 절차는 접근이 통제된 서버 한 대에서 실행하는 것을 전제로 합니다. 각 명령 블록 앞에는 운영 환경에서 그 작업이 어느 쪽에 속하는지를 실행 위치로 표시했습니다. 운영 환경에서 두 시스템을 분리한다면, "키 보유 측 작업"으로 표시된 단계는 키를 보유한 시스템에서 수행하고 HSM 클라이언트 서버로는 감싼 결과물(.oaep.bin, .kwp.bin)과 공개키(.pub.pem)만 전달하십시오. 평문 키 파일은 HSM 클라이언트 서버에 두지 마십시오.

:::

지원 환경

전제 조건

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

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

지원 버전

구분 버전 비고
HSM 클라이언트(Luna Client) 10.9.3 cmu, ckdemo가 함께 설치됩니다.
OpenSSL (키 보유 측) 3.0 이상 RSA/EC 개인키를 AES-KWP로 감쌀 때 필요합니다.

이 문서의 절차는 Linux 서버의 bash를 기준으로 작성했습니다.

키 보유 측에서는 OpenSSL로 키를 감쌉니다. AES 키를 RSA-OAEP로 감싸는 작업은 OpenSSL 1.1.1에서도 되지만, RSA/EC 개인키를 AES-KWP로 감싸는 openssl enc -id-aes256-wrap-pad는 OpenSSL 3.0 이상에서만 동작합니다. 이 문서의 예제는 OpenSSL 3.0.15에서 확인했습니다.

:::(info) (참고)
키 보유 측에서 OpenSSL 3.0 이상을 사용할 수 없다면, 개인키 감싸기만 Java로 수행할 수 있습니다. BYOK(키 주입) Java 예제의 KwpWrap.java를 사용하십시오(JDK 17 이상 필요).

:::

실행 공통값

실행 위치 — HSM 클라이언트 서버

이후의 모든 명령에서 사용할 값입니다. SLOT과 CO_PW를 실제 파티션의 슬롯 번호와 Crypto Officer(CO) 비밀번호로 교체하십시오. 키 생성·주입·속성 변경은 CO 권한이 필요합니다. Crypto User(CU)로 로그인하면(cmu의 -cu 옵션) 조회는 되지만 키쌍 생성(Key pair generation failed), 공개키 주입(Create object failed), 속성 변경(Failed to set attribute value)이 모두 실패합니다.

export SLOT=0
read -rsp "CO password: " CO_PW; echo; export CO_PW
export TS=${TS:-$(date +%Y%m%d%H%M%S)}; echo "TS=$TS"
export PATH=/usr/safenet/lunaclient/bin:$PATH

CO_PW는 화면과 셸 기록에 남지 않도록 입력받습니다. TS는 이 문서의 예제에서 label과 파일 이름이 겹치지 않도록 붙이는 타임스탬프입니다. 이미 TS가 설정되어 있으면(예: BYOK(키 주입) 키 준비를 먼저 진행한 경우) 그 값을 그대로 사용합니다. 아래 절차를 이어서 진행하는 동안 값을 바꾸지 마십시오.

셸을 하나 더 열어 두십시오. 이후 절차에서는 한 셸에서 ckdemo를 실행해 둔 채, 다른 셸에서 cmu와 OpenSSL 명령을 실행합니다. 새로 연 셸에는 위 변수가 없으므로, 새 셸에서도 위 블록을 실행하되 TS는 첫 셸에 출력된 값으로 지정하십시오(export TS=<첫 셸의 값>을 먼저 실행한 뒤 위 블록을 실행). 키 보유 측을 다른 시스템에서 작업할 때도 같은 TS 값을 지정해야 파일 이름이 맞습니다.

SLOT이 대상 파티션을 가리키는지 확인합니다. 로그인에 성공하고 그 파티션의 객체 목록(비어 있으면 출력 없음)이 나오면 정상입니다.

cmu list -slot=$SLOT -password="$CO_PW" -display=handle,label </dev/null

:::(warning) (주의)

  • HA(고가용성) 그룹으로 구성된 경우 SLOT에는 개별 파티션 슬롯이 아니라 HA 가상 슬롯 번호를 지정해야 합니다. 개별 슬롯에 주입하면 그 파티션에만 키가 생기고 HA 멤버 간에 동기화되지 않습니다. cmu와 ckdemo가 사용하는 슬롯 번호는 lunacm:> slot list가 표시하는 번호와 다를 수 있으므로, 위 cmu list로 대상 파티션이 맞는지 확인하십시오.
  • 로그인 실패가 파티션 정책 20(Max failed user logins allowed)의 횟수만큼 이어지면 역할이 잠길 수 있습니다. 비밀번호가 틀렸다는 오류가 나면 비밀번호를 확인한 뒤 다시 시도하십시오.
  • 이 문서의 cmu 명령은 비밀번호를 명령행 인자(-password)로 전달하므로, 명령이 실행되는 동안 프로세스 목록에 보일 수 있습니다. 다른 사용자가 접속하지 않는 서버에서 실행하고, 작업이 끝나면 unset CO_PW로 변수를 지우십시오.

:::

이 문서의 cmu 명령은 모두 끝에 </dev/null을 붙입니다. 필요한 옵션이 빠지면 cmu가 대화형 질문을 띄우고 입력을 기다리는데, </dev/null을 붙이면 기다리지 않고 바로 실패하므로 빠진 옵션을 알아차리기 쉽습니다.

샘플 키 준비

이 문서의 절차를 그대로 따라 해 볼 수 있도록, 주입할 샘플 키를 만듭니다. 실제로 주입할 키를 이미 파일로 보유하고 계시면 이 절을 건너뛰고 이후 명령에서 # 준비한 키 사용 시 주석이 달린 줄의 파일 경로만 보유하신 키 파일로 바꾸시면 됩니다. 파일 형식은 주입 입력 형식을 참고하십시오. 키가 애플리케이션 코드의 키 객체로만 있다면 BYOK(키 주입) 키 준비를 참고해 파일로 내보내십시오.

실행 위치 — 키 보유 측 작업 (이 예제에서는 같은 서버에서 실행)

umask 077   # 이 셸에서 만드는 파일을 소유자만 읽고 쓸 수 있게 함

# AES-256 대칭키 (32바이트)
openssl rand -out "/tmp/sample-aes-$TS.bin" 32

# RSA-2048 키쌍 — 개인키, 공개키, 주입용 PKCS#8 DER
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "/tmp/sample-rsa-$TS.pem"
openssl rsa -in "/tmp/sample-rsa-$TS.pem" -pubout -out "/tmp/sample-rsa-$TS.pub.pem"
openssl pkcs8 -topk8 -nocrypt \
  -in "/tmp/sample-rsa-$TS.pem" -outform DER -out "/tmp/sample-rsa-$TS.pk8.der"

# EC P-256 키쌍 — 개인키, 공개키, 주입용 PKCS#8 DER
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "/tmp/sample-ec-$TS.pem"
openssl ec -in "/tmp/sample-ec-$TS.pem" -pubout -out "/tmp/sample-ec-$TS.pub.pem"
openssl pkcs8 -topk8 -nocrypt \
  -in "/tmp/sample-ec-$TS.pem" -outform DER -out "/tmp/sample-ec-$TS.pk8.der"

:::(warning) (주의)
sample-aes-$TS.bin, sample-{rsa,ec}-$TS.pem, sample-{rsa,ec}-$TS.pk8.der는 키 평문입니다. 공개키 파일(.pub.pem)만 평문이 아닙니다. 운영 환경에서 두 시스템을 분리한다면 이 파일들을 HSM 클라이언트 서버로 옮기지 마십시오. 모든 절차가 끝나면 정리에서 삭제하십시오.

:::

1단계 — wrapping 키쌍 생성과 공개키 반출

공개키만 주입하는 경우를 제외하면, 모든 주입 시나리오는 이 단계에서 시작합니다. 파티션 안에 RSA wrapping 키쌍을 만들고 공개키만 PEM으로 반출합니다.

실행 위치 — HSM 클라이언트 서버

cmu generatekeypair -slot=$SLOT -password="$CO_PW" \
  -keytype=rsa -mech=pkcs -modulusbits=2048 -publicexponent=65537 \
  -labelpublic="byok-wrap-$TS-pub" -labelprivate="byok-wrap-$TS-prv" \
  -wrap=1 -encrypt=1 -unwrap=1 -decrypt=1 -sign=0 -verify=0 -extractable=0 </dev/null
...The key pair was successfully generated -> public handle(488), private handle(703)

cmu는 지정한 속성을 키 종류에 맞게 나누어 적용합니다. 공개키에는 wrap·encrypt가, 개인키에는 unwrap·decrypt·extractable=false가 지정됩니다. 위 출력의 handle 번호는 예시이며 실제 값은 파티션마다 다릅니다. 출력된 두 handle을 변수로 기록해 두십시오. 이후 명령에서 사용하며, 셸을 두 개 쓰는 경우 양쪽 셸에서 모두 지정하십시오.

export WRAP_PUB=<출력된 public handle>
export WRAP_PRV=<출력된 private handle>

개인키의 속성을 확인합니다.

cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$WRAP_PRV </dev/null \
  | grep -E '^(class|label|sensitive|extractable|unwrap|decrypt|sign)='
class=privateKey
label=byok-wrap-20261012203225-prv
sensitive=true
decrypt=true
unwrap=true
sign=false
extractable=false

extractable=false이므로 wrapping 개인키는 파티션 밖으로 나갈 수 없습니다. 공개키를 PEM으로 반출합니다.

cmu export -slot=$SLOT -password="$CO_PW" -handle=$WRAP_PUB -key \
  -outputfile="/tmp/byok-wrap-$TS.pem" </dev/null
head -1 "/tmp/byok-wrap-$TS.pem"
-----BEGIN PUBLIC KEY-----

출력 파일에서 -----BEGIN PUBLIC KEY-----가 확인되면 정상입니다. -key는 지정한 handle이 인증서가 아니라 공개키임을 알리는 옵션입니다. 운영 환경에서 두 시스템을 분리한다면 /tmp/byok-wrap-$TS.pem 파일을 키 보유 측에 전달하십시오. 공개키이므로 별도의 보호 없이 전달해도 됩니다.

ckdemo 준비

감싼 키를 파티션 안에서 푸는(unwrap) 작업은 ckdemo로 수행합니다. ckdemo는 메뉴 번호를 입력하는 대화형 도구이며, 아래 설정은 ckdemo를 실행할 때마다 다시 해야 합니다.

실행 위치 — HSM 클라이언트 서버

cd /tmp && ckdemo

:::(warning) (주의)
ckdemo는 암호화 결과 등을 현재 디렉터리에 파일로 씁니다. 쓰기 권한이 없는 디렉터리(예: /usr/safenet/lunaclient/bin)에서 실행하면 연산은 성공해도 WriteBinaryFile returned error. (CKR_DEVICE_ERROR)로 실패합니다. 위와 같이 쓰기 권한이 있는 디렉터리에서 실행하십시오.

:::

1. OAEP 해시 설정 — 98 (Options)을 입력하고, 옵션 목록에서 17을 입력해 17 - OAEP Hash Params를 selectable로 바꾼 뒤 0(Finished)으로 나옵니다.

17 - OAEP Hash Params           : selectable

:::(warning) (주의)
ckdemo의 OAEP 해시 기본값은 SHA-1입니다(use default (SHA1 Digest and MGF1)). 이 문서는 키를 SHA-256 OAEP로 감싸므로, 이 설정을 바꾸지 않으면 unwrap이 CKR_GENERAL_ERROR로 실패합니다. 설정은 ckdemo를 종료하면 기본값으로 돌아가므로 실행할 때마다 다시 바꾸십시오. unwrap에서 mechanism 26을 고른 직후 Mechanism to use 질문 없이 바로 Enter filename of OAEP Source Data가 나온다면 이 설정이 빠진 것입니다. 진행하지 말고 Ctrl+C로 중단하십시오. Ctrl+C는 ckdemo를 종료하므로, cd /tmp && ckdemo로 다시 실행한 뒤 이 절의 1·2(OAEP 해시 설정, 세션 열기와 로그인)를 처음부터 다시 수행하십시오. 이미 주입한 키는 영구 객체이므로 ckdemo를 종료해도 남아 있습니다.

:::

2. 세션 열기와 로그인 — 1 (Open Session)에서 슬롯 번호를 입력하고, 3 (Login)에서 Crypto Officer를 선택해 비밀번호를 입력합니다. 자세한 화면은 HSM CLI 도구 사용 가이드를 참고해 주십시오.

이후 절의 ckdemo 작업은 이 세션에서 이어서 진행합니다. cmu와 OpenSSL 명령은 실행 공통값에서 열어 둔 다른 셸에서 실행합니다.

AES-256 대칭키 주입

AES 대칭키는 HSM wrapping 공개키로 한 번 감싸서 주입합니다.

키 보유 측 — AES 키 감싸기

실행 위치 — 키 보유 측 작업 (이 예제에서는 같은 서버에서 실행)

# 준비한 키 사용 시: -in 을 보유한 AES-256 키 파일(원시 32바이트)로
openssl pkeyutl -encrypt \
  -pubin -inkey "/tmp/byok-wrap-$TS.pem" \
  -in "/tmp/sample-aes-$TS.bin" \
  -out "/tmp/sample-aes-$TS.oaep.bin" \
  -pkeyopt rsa_padding_mode:oaep \
  -pkeyopt rsa_oaep_md:sha256 \
  -pkeyopt rsa_mgf1_md:sha256
ls -l "/tmp/sample-aes-$TS.oaep.bin"     # 256바이트

:::(warning) (주의)
rsa_oaep_md와 rsa_mgf1_md를 모두 sha256으로 지정해야 합니다. OpenSSL 기본값은 SHA-1이라, 생략하면 HSM에서 unwrap이 실패합니다.

:::

운영 환경에서 두 시스템을 분리한다면 /tmp/sample-aes-$TS.oaep.bin 파일만 HSM 클라이언트 서버로 전달하십시오.

파티션에 주입

실행 위치 — HSM 클라이언트 서버 (ckdemo)

61 (Unwrap key)을 입력하고 아래와 같이 답합니다.

질문 입력 설명
Select mechanism for unwrapping 26 RSA_OAEP
Mechanism to use 3 SHA256 (OAEP 해시). MGF1 해시도 같은 값이 사용되며 따로 묻지 않습니다
Enter filename of OAEP Source Data [0 for none] 0 label 없음
Type of key to be unwrapped 9 AES
Token attribute 1 파티션에 영구 저장
Sensitive attribute 1 키 값을 조회할 수 없게 함
Private attribute 1 로그인해야 접근 가능
Encrypt attribute 1 암호화·복호화 허용(CKA_ENCRYPT/CKA_DECRYPT)
Sign attribute 0
Wrap attribute 0 다른 키 감싸기·풀기 불허(CKA_WRAP/CKA_UNWRAP)
Derive attribute 0
Modifiable attribute 1 이후 속성 변경 허용
Extractable attribute 0 파티션 밖으로 반출 불허
Enter handle of unwrapping key WRAP_PRV에 기록한 번호 1단계의 wrapping 개인키 handle
Enter the length in bytes of the key being unwrapped 32 AES-256
Enter filename with key to unwrap /tmp/sample-aes-{TS}.oaep.bin {TS}는 실제 값으로
Unwrapped key is        588 (0x0000024c)
Status: Doing great, no errors (CKR_OK)

대칭키에서는 Encrypt attribute 질문 하나가 CKA_ENCRYPT와 CKA_DECRYPT에, Wrap attribute 질문 하나가 CKA_WRAP과 CKA_UNWRAP에 함께 적용됩니다. 질문 순서는 ckdemo 버전에 따라 다를 수 있으므로 질문 이름을 보고 답하십시오.

위 handle 번호는 예시입니다. 출력된 handle을 기록하고, 다른 셸에서 label을 지정합니다. ckdemo로 주입한 키에는 Unwrapped AES Key 같은 고정 label이 붙습니다. label을 바꾸기 전에, 기록한 handle이 방금 주입한 키가 맞는지(label=Unwrapped AES Key) 확인하십시오.

실행 위치 — HSM 클라이언트 서버 (cmu)

export AES_KEY=<출력된 handle>
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$AES_KEY </dev/null | grep -E '^(class|label)='
cmu setattribute -slot=$SLOT -password="$CO_PW" -handle=$AES_KEY -label="aes-$TS" </dev/null

주입한 키 확인

주입한 키가 원래 키와 같은지, 같은 평문을 HSM 안의 키와 원래 키로 각각 암호화해 비교합니다. 이 확인은 선택 사항입니다.

실행 위치 — HSM 클라이언트 서버

head -c 16 /dev/zero > /tmp/zero16.bin

ckdemo에서 40 (Encrypt file)을 입력하고, mechanism 27 (AES-ECB), 파일 /tmp/zero16.bin, 키 handle($AES_KEY 값)을 차례로 입력합니다. 결과는 현재 디렉터리에 ENCRYPT.BIN으로 저장됩니다.

실행 위치 — 키 보유 측 작업 (이 예제에서는 같은 서버에서 실행)

# 준비한 키 사용 시: -K 의 /tmp/sample-aes-$TS.bin 을 보유한 AES 키 파일로
openssl enc -aes-256-ecb -nopad \
  -K "$(od -An -tx1 /tmp/sample-aes-$TS.bin | tr -d ' \n')" -in /tmp/zero16.bin \
  | cmp - /tmp/ENCRYPT.BIN && echo SAME
SAME

SAME이 출력되면 원래 키가 그대로 주입된 것입니다.

:::(warning) (주의)
위 openssl enc -K는 AES 키 값을 명령행 인자로 넘기므로, 명령이 실행되는 동안 프로세스 목록에 키 값이 보일 수 있습니다. 다른 사용자가 접속하지 않는 키 보유 측 시스템에서만 실행하십시오.

:::
이 확인은 원래 키를 보유한 쪽에서만 할 수 있으므로, 운영 환경에서 두 시스템을 분리한다면 ENCRYPT.BIN을 키 보유 측으로 전달해 비교하십시오. 확인이 끝나면 /tmp/zero16.bin과 /tmp/ENCRYPT.BIN을 삭제하십시오.

RSA 키 주입

RSA 개인키는 크기 때문에 RSA-OAEP로 직접 감쌀 수 없습니다(RSA-2048 기준 최대 190바이트). 임시 AES KEK로 개인키를 AES-KWP로 감싸고, 그 KEK만 HSM 공개키로 감싸서 전달합니다. 파티션 안에서는 unwrap을 두 번 수행합니다. 공개키는 감싸지 않고 그대로 주입합니다.

키 보유 측 — 임시 KEK 생성과 개인키 감싸기

실행 위치 — 키 보유 측 작업 (이 예제에서는 같은 서버에서 실행)

# 1. 임시 AES-256 KEK 생성
umask 077
openssl rand -out "/tmp/kek-$TS.bin" 32

# 2. KEK 를 HSM 공개키로 RSA-OAEP 감싸기
openssl pkeyutl -encrypt \
  -pubin -inkey "/tmp/byok-wrap-$TS.pem" \
  -in "/tmp/kek-$TS.bin" -out "/tmp/kek-$TS.oaep.bin" \
  -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256

# 3. RSA 개인키를 KEK 로 AES-KWP 감싸기 (OpenSSL 3.0 이상)
# 준비한 키 사용 시: -in 을 보유한 RSA 개인키 PKCS#8 DER 파일로
openssl enc -id-aes256-wrap-pad \
  -K "$(od -An -tx1 /tmp/kek-$TS.bin | tr -d ' \n')" -iv A65959A6 \
  -in "/tmp/sample-rsa-$TS.pk8.der" -out "/tmp/sample-rsa-$TS.kwp.bin"

:::(warning) (주의)

  • 3번은 OpenSSL 1.1.1에서 wrap mode not allowed 오류로 실패합니다. openssl version으로 3.0 이상인지 확인하고, OpenSSL 3.0 이상에서 실행하십시오. EC 키 주입의 감싸기 명령도 같습니다.
  • 3번의 -K는 KEK 값을 명령행 인자로 넘기므로, 명령이 실행되는 동안 프로세스 목록에 KEK 값이 보일 수 있습니다. 다른 사용자가 접속하지 않는 키 보유 측 시스템에서 실행하십시오. 이 노출을 피하려면 파일에서 KEK를 읽는 BYOK(키 주입) Java 예제의 KwpWrap.java로 감싸십시오.

:::

운영 환경에서 두 시스템을 분리한다면 /tmp/kek-$TS.oaep.bin, /tmp/sample-rsa-$TS.kwp.bin, /tmp/sample-rsa-$TS.pub.pem만 HSM 클라이언트 서버로 전달하십시오. kek-$TS.bin은 KEK 평문입니다.

파티션에 임시 KEK 주입

실행 위치 — HSM 클라이언트 서버 (ckdemo)

61 (Unwrap key)을 입력합니다. AES 키 주입과 같은 순서로 답하되, 아래 항목만 다르게 입력합니다.

질문 입력 설명
Encrypt attribute 0 KEK는 암호화·복호화에 쓰지 않음
Wrap attribute 1 개인키를 풀기 위해 CKA_UNWRAP이 필요
Enter filename with key to unwrap /tmp/kek-{TS}.oaep.bin
Unwrapped key is        642 (0x00000282)
Status: Doing great, no errors (CKR_OK)

Wrap attribute는 CKA_WRAP과 CKA_UNWRAP을 함께 켭니다. KEK에는 CKA_UNWRAP만 필요하므로, 다른 셸에서 CKA_WRAP을 끕니다. 먼저 기록한 handle이 방금 주입한 키(label=Unwrapped AES Key)가 맞는지 확인하십시오.

실행 위치 — HSM 클라이언트 서버 (cmu)

export KEK=<출력된 handle>
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$KEK </dev/null | grep -E '^(class|label)='
cmu setattribute -slot=$SLOT -password="$CO_PW" -handle=$KEK -wrap=0 -label="kek-$TS" </dev/null
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$KEK </dev/null | grep -E '^(encrypt|decrypt|wrap|unwrap)='
encrypt=false
decrypt=false
wrap=false
unwrap=true

파티션에 RSA 개인키 주입

실행 위치 — HSM 클라이언트 서버 (ckdemo)

61 (Unwrap key)을 입력하고 아래와 같이 답합니다.

질문 입력 설명
Select mechanism for unwrapping 31 AES-KWP
Use external IV? 0 RFC 5649 기본 IV 사용
Type of key to be unwrapped 4 RSA Private
Token attribute / Sensitive attribute / Private attribute 1 / 1 / 1
Encrypt attribute 1 개인키에서는 RSA-OAEP 복호화 허용(CKA_DECRYPT)
Sign attribute 1 서명 허용
Wrap attribute 0 개인키에서는 CKA_UNWRAP 불허
Derive attribute 0
Modifiable attribute 1
Extractable attribute 0
Enter handle of unwrapping key KEK에 기록한 번호 바로 앞에서 주입한 KEK
Enter filename with key to unwrap /tmp/sample-rsa-{TS}.kwp.bin
Unwrapped key is        448 (0x000001c0)
Status: Doing great, no errors (CKR_OK)

AES-KWP는 풀 때 무결성을 검사하므로, KEK가 다르거나 감싼 데이터가 손상되면 unwrap 자체가 실패합니다. 다른 셸에서 label을 지정하고, 주입한 개인키의 modulus가 원래 키와 같은지 확인합니다.

실행 위치 — HSM 클라이언트 서버 (cmu)

export RSA_PRV=<출력된 handle>
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$RSA_PRV </dev/null | grep -E '^(class|label)='
cmu setattribute -slot=$SLOT -password="$CO_PW" -handle=$RSA_PRV -label="rsa-prv-$TS" </dev/null

# 주입한 개인키의 modulus 를 파일로 저장 (공개 정보)
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$RSA_PRV </dev/null \
  | sed -n 's/^modulus=//p' | tr a-f A-F > "/tmp/rsa-prv-$TS.modulus"

원래 공개키의 modulus와 비교합니다. 공개키만 있으면 되므로 HSM 클라이언트 서버에서도 실행할 수 있습니다. 두 시스템을 분리했다면 /tmp/rsa-prv-$TS.modulus 파일을 키 보유 측으로 옮겨 비교해도 됩니다.

실행 위치 — HSM 클라이언트 서버 또는 키 보유 측 작업

# 준비한 키 사용 시: -in 을 보유한 RSA 공개키 PEM 파일로
openssl rsa -pubin -in "/tmp/sample-rsa-$TS.pub.pem" -noout -modulus | sed 's/^Modulus=//' \
  | diff - "/tmp/rsa-prv-$TS.modulus" && echo SAME
SAME

파티션에 RSA 공개키 주입

공개키는 공개 정보이므로 감쌀 필요 없이 PEM 파일을 그대로 주입합니다.

실행 위치 — HSM 클라이언트 서버

# 준비한 키 사용 시: -inputfile 을 보유한 RSA 공개키 PEM 파일로
cmu import -slot=$SLOT -password="$CO_PW" \
  -inputfile="/tmp/sample-rsa-$TS.pub.pem" -pubkey=rsa -label="rsa-pub-$TS" </dev/null
...The RSA public key object was successfully created -> handle(439)

cmu import로 주입한 공개키는 verify=true, encrypt=false, wrap=false로 만들어집니다. RSA 공개키로 암호화도 수행하려면 출력된 handle로 속성을 켜십시오. 먼저 그 handle이 방금 주입한 공개키(label=rsa-pub-$TS)가 맞는지 확인하십시오.

export RSA_PUB=<출력된 handle>
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$RSA_PUB </dev/null | grep -E '^(class|label)='
cmu setattribute -slot=$SLOT -password="$CO_PW" -handle=$RSA_PUB -encrypt=1 </dev/null

EC 키 주입

EC 개인키도 RSA 개인키와 같은 방식(임시 KEK + AES-KWP)으로 주입합니다. 아래는 RSA와 다른 부분만 안내합니다. AES-KWP는 풀 때 무결성을 검사하므로, unwrap이 CKR_OK로 끝나면 감싼 개인키가 손상 없이 주입된 것입니다.

키 보유 측 — 개인키 감싸기

실행 위치 — 키 보유 측 작업 (이 예제에서는 같은 서버에서 실행)

RSA 키 주입에서 만든 임시 KEK를 다시 사용합니다. RSA 키를 주입하지 않았다면 RSA 키 보유 측 작업의 1~2번으로 KEK를 먼저 만들고 임시 KEK 주입을 수행하십시오.

# 준비한 키 사용 시: -in 을 보유한 EC 개인키 PKCS#8 DER 파일로
openssl enc -id-aes256-wrap-pad \
  -K "$(od -An -tx1 /tmp/kek-$TS.bin | tr -d ' \n')" -iv A65959A6 \
  -in "/tmp/sample-ec-$TS.pk8.der" -out "/tmp/sample-ec-$TS.kwp.bin"

파티션에 EC 개인키·공개키 주입

실행 위치 — HSM 클라이언트 서버 (ckdemo)

RSA 개인키 주입과 같은 순서로 답하되, 아래 항목만 다르게 입력합니다.

질문 입력 설명
Type of key to be unwrapped 7 ECDSA Private
Encrypt attribute 0 EC 개인키는 복호화에 쓰지 않음
Enter filename with key to unwrap /tmp/sample-ec-{TS}.kwp.bin
Unwrapped key is        445 (0x000001bd)
Status: Doing great, no errors (CKR_OK)

실행 위치 — HSM 클라이언트 서버 (cmu)

export EC_PRV=<출력된 handle>
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$EC_PRV </dev/null | grep -E '^(class|label)='
cmu setattribute -slot=$SLOT -password="$CO_PW" -handle=$EC_PRV -label="ec-prv-$TS" </dev/null

# 준비한 키 사용 시: -inputfile 을 보유한 EC 공개키 PEM 파일로
cmu import -slot=$SLOT -password="$CO_PW" \
  -inputfile="/tmp/sample-ec-$TS.pub.pem" -pubkey=ecdsa -label="ec-pub-$TS" </dev/null
...The ECDSA public key object was successfully created -> handle(444)

주입한 키 조회

주입한 키가 영구(token) 객체로 기록됐는지, 의도한 용도 속성이 붙었는지 확인합니다.

실행 위치 — HSM 클라이언트 서버

cmu list -slot=$SLOT -password="$CO_PW" -display=handle,label </dev/null | grep -- "-$TS"
cmu getattribute -slot=$SLOT -password="$CO_PW" -handle=$EC_PRV </dev/null

EC 개인키의 출력 예시입니다(일부 생략).

class=privateKey
token=true
private=true
label=ec-prv-20261012203225
keytype=ECDSA
sensitive=true
decrypt=false
unwrap=false
sign=true
derive=false
extractable=false
local=false
neverextractable=false
alwayssensitive=false
modifiable=true

local=false, neverextractable=false, alwayssensitive=false는 파티션 밖에서 만들어 주입한 키라서 나타나는 정상 값입니다. 파티션 안에서 생성한 키(예: 1단계의 wrapping 개인키)는 local=true, neverextractable=true로 표시됩니다.

정리

개인키 주입에 사용한 임시 KEK는 주입이 끝나면 더 이상 필요하지 않습니다. 삭제하십시오.

실행 위치 — HSM 클라이언트 서버

cmu list -slot=$SLOT -password="$CO_PW" -display=handle,label </dev/null | grep -- "kek-$TS"
cmu delete -slot=$SLOT -password="$CO_PW" -handle=<위에서 조회한 handle> -force </dev/null

handle은 세션 안에서만 유지되고, 객체를 삭제하면 다른 객체의 handle이 바뀔 수 있습니다. 앞에서 기록한 $KEK를 쓰지 말고, 삭제 직전에 cmu list로 handle을 다시 조회해 바로 사용하십시오. 대칭키는 cmu delete에 label로 지정할 수 없습니다. -force는 확인 질문 없이 삭제하므로, 조회 결과의 label이 kek-$TS인지 확인하십시오. AES 키만 주입해 KEK를 만들지 않았다면 이 단계는 건너뛰십시오.

wrapping 키쌍(byok-wrap-$TS-pub / -prv)은 이후 추가 주입에 재사용할 수 있으므로 남겨두어도 됩니다.

키 보유 측의 평문 키 파일과 중간 산출물도 삭제하십시오.

보유하신 키 파일의 경로를 직접 지정해 진행했다면, 아래 rm의 경로를 그 파일로 바꾸지 마십시오. 원본 키 파일이 삭제됩니다. 아래 명령은 이 문서에서 만든 샘플 키·평문 사본과 중간 산출물만 지웁니다.

실행 위치 — 키 보유 측 작업 (이 예제에서는 같은 서버에서 실행)

rm -f "/tmp/sample-aes-$TS.bin" \
      "/tmp/sample-rsa-$TS.pem" "/tmp/sample-rsa-$TS.pk8.der" \
      "/tmp/sample-ec-$TS.pem" "/tmp/sample-ec-$TS.pk8.der" \
      "/tmp/kek-$TS.bin" "/tmp/kek-$TS.oaep.bin" \
      "/tmp/sample-aes-$TS.oaep.bin" \
      "/tmp/sample-rsa-$TS.kwp.bin" "/tmp/sample-ec-$TS.kwp.bin" \
      "/tmp/rsa-prv-$TS.modulus" \
      /tmp/zero16.bin /tmp/ENCRYPT.BIN

일부 키 유형만 주입했다면 해당하지 않는 파일은 무시하십시오. rm -f는 없는 파일이 있어도 오류 없이 넘어갑니다.

이 문서의 절차를 시험 삼아 실행하신 경우, 주입한 키도 정리하실 수 있습니다. 개인키와 공개키는 label로 지정해 삭제합니다.

cmu delete -slot=$SLOT -password="$CO_PW" -privatelabel="rsa-prv-$TS" -force </dev/null   # RSA 개인키
cmu delete -slot=$SLOT -password="$CO_PW" -publiclabel="rsa-pub-$TS" -force </dev/null    # RSA 공개키
cmu delete -slot=$SLOT -password="$CO_PW" -privatelabel="ec-prv-$TS" -force </dev/null    # EC 개인키
cmu delete -slot=$SLOT -password="$CO_PW" -publiclabel="ec-pub-$TS" -force </dev/null     # EC 공개키

AES 키는 label로 지정할 수 없으므로, 삭제 직전에 handle을 다시 조회하고 label이 aes-$TS인지 확인한 뒤 바로 삭제하십시오.

cmu list -slot=$SLOT -password="$CO_PW" -display=handle,label </dev/null | grep -- "aes-$TS"
cmu delete -slot=$SLOT -password="$CO_PW" -handle=<위에서 조회한 handle> -force </dev/null

모든 작업이 끝나면 비밀번호 변수를 지우십시오.

unset CO_PW

문제 해결

ckdemo unwrap에서 CKR_GENERAL_ERROR

ckdemo의 OAEP 해시가 기본값(SHA-1)으로 남아 있는 경우입니다. 98 (Options)에서 17 - OAEP Hash Params를 selectable로 바꾼 뒤, unwrap할 때 Mechanism to use에서 3 (SHA256)을 선택하십시오. 이 설정은 ckdemo를 다시 실행하면 기본값으로 돌아갑니다. 설정을 바꿨는데도 실패한다면, 키 보유 측에서 rsa_oaep_md와 rsa_mgf1_md를 모두 sha256으로 지정했는지 확인하십시오.

ckdemo에서 WriteBinaryFile returned error. (CKR_DEVICE_ERROR)

ckdemo를 쓰기 권한이 없는 디렉터리에서 실행한 경우입니다. ckdemo를 종료하고 /tmp 등 쓰기 권한이 있는 디렉터리로 이동해 다시 실행하십시오. 이미 주입한 키는 영구 객체이므로 ckdemo를 종료해도 남아 있습니다.

openssl enc에서 wrap mode not allowed

OpenSSL 1.1.1에서 AES-KWP 감싸기를 시도한 경우입니다. OpenSSL 3.0 이상을 사용하거나, BYOK(키 주입) Java 예제의 KwpWrap.java로 감싸십시오.

cmu 명령이 아무 출력 없이 멈춤

필요한 옵션이 빠져 cmu가 대화형 질문을 기다리는 경우입니다. 예를 들어 RSA 키쌍 생성에서 -mech를 빠뜨리면 메커니즘 종류를 묻습니다. Ctrl+C로 중단하고, 명령 끝에 </dev/null을 붙여 다시 실행하면 빠진 옵션이 오류로 드러납니다.

RSA/EC 개인키 unwrap에서 CKR_ENCRYPTED_DATA_INVALID 또는 CKR_WRAPPED_KEY_INVALID

KEK가 일치하지 않거나 감싼 파일이 손상된 경우입니다. Enter handle of unwrapping key에 이번 작업에서 주입한 KEK의 handle을 입력했는지, /tmp/kek-$TS.oaep.bin과 /tmp/sample-*-$TS.kwp.bin을 같은 kek-$TS.bin으로 만들었는지 확인하십시오.

다음 단계