---
title: "C++ 예제"
slug: "hsm-vpc-pqc-cpp"
updated: 2026-07-23T09:02:56Z
published: 2026-07-23T09:02:56Z
canonical: "guide-fin.ncloud-docs.com/hsm-vpc-pqc-cpp"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://guide-fin.ncloud-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# C++ 예제

Ncloud Hardware Security Module(HSM) 서비스는 Thales Luna HSM을 기반으로 하며, NIST 표준 양자내성암호(PQC) 알고리즘인 ML-KEM(FIPS 203, 키 캡슐화), ML-DSA (FIPS 204, 전자서명), HSS/LMS (NIST SP 800-208, RFC 8554, 상태 유지형 전자서명)를 PKCS#11(Cryptoki) C/C++ API로 지원합니다.

이 문서에서는 C++ 애플리케이션에서 Cryptoki API로 PQC 키를 생성하고, 키 캡슐화(KEM)·전자서명을 수행하는 방법을 안내합니다.

참고

ML-KEM/ML-DSA는 [Java 예제 가이드](/docs/hsm-vpc-pqc-java)로도 사용할 수 있습니다. 전체 PQC 기능 목록은 [양자내성암호(PQC) — 개요](/docs/hsm-vpc-pqc)를 참고해 주십시오.

이 문서의 mechanism/attribute 명세는 Thales 공식 문서를 기준으로 작성되었습니다.

- [ML-KEM Programming Guide (Luna SDK Reference)](https://www.thalesdocs.com/gphsm/luna/7/docs/network/Content/sdk/extensions/pqc/ML-KEM_programming_guide.htm)
- [ML-DSA Programming Guide (Luna SDK Reference)](https://www.thalesdocs.com/gphsm/luna/7/docs/network/Content/sdk/extensions/pqc/ML-DSA_programming_guide.htm)
- [LMS and HSS (Luna SDK Reference)](https://www.thalesdocs.com/gphsm/luna/7/docs/network/Content/sdk/extensions/pqc/LMS_and_HSS.htm)

## 지원 환경

### 전제 조건

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

- HSM 클라이언트 서버에 C/C++ 빌드 도구(`g++`)가 설치되어 있음
- 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) |

## 빌드 방법

Cryptoki 라이브러리(`libCryptoki2_64.so`)는 컴파일 시점에 링크하지 않고, 런타임에 `dlopen`으로 불러와 사용합니다. 아래 플래그로 컴파일하십시오.

```
g++ -DUNIX -DOS_UNIX \
    -I/usr/safenet/lunaclient/sdk/include \
    -I/usr/safenet/lunaclient/sdk/external \
    -o hss_keygen_example hss_keygen_example.cpp \
    -ldl
```

다른 예제도 소스 파일명만 바꾸면 동일합니다.

## 세션 초기화 및 파티션 로그인

아래는 모든 예제 코드에 공통으로 포함되는 부분입니다. Cryptoki 라이브러리를 `dlopen`으로 불러오고, 세션을 열어 파티션에 로그인합니다.

```
#include "cryptoki_v2.h"
#include <dlfcn.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>

// libCryptoki2_64.so를 런타임에 로드하고 함수 포인터 테이블(p11)을 가져온다
const char *libPath = getenv("SfntLibPath");
void *libHandle = dlopen(libPath, RTLD_NOW);
CK_C_GetFunctionList C_GetFunctionList = (CK_C_GetFunctionList)dlsym(libHandle, "C_GetFunctionList");
CK_FUNCTION_LIST_PTR p11 = NULL;
C_GetFunctionList(&p11);

// 세션 오픈 및 파티션 로그인
CK_SESSION_HANDLE hSession;
p11->C_Initialize(NULL_PTR);
p11->C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
p11->C_Login(hSession, CKU_USER, pin, (CK_ULONG)strlen((char *)pin));
```

실행 전 라이브러리 경로를 `SfntLibPath` 환경변수로 지정해야 합니다.

```
export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so
```

## ML-KEM 키 생성 및 키 캡슐화(KEM)

ML-KEM 키 쌍은 `CKM_ML_KEM_KEY_PAIR_GEN` mechanism으로 생성하고, `CKA_PARAMETER_SET`으로 파라미터 세트(512/768/1024)를 지정합니다. 캡슐화·역캡슐화는 `C_EncapsulateKey`/`C_DecapsulateKey`(PKCS#11 3.2 정식 표준)가 아직 정식 도입되기 전이라, Thales 독자 함수인 `CA_EncapsulateKey`/`CA_DecapsulateKey`를 사용합니다. 이 두 함수는 `CK_FUNCTION_LIST`(함수 포인터 테이블)에 없으므로 `dlsym`으로 직접 심볼을 가져와야 합니다. `mlkem_example.cpp` 파일을 아래 내용으로 생성하십시오.

```
#include "cryptoki_v2.h"
#include <dlfcn.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>

// CA_EncapsulateKey/CA_DecapsulateKey는 함수 테이블에 없어 dlsym으로 직접 가져와야 하므로 타입을 직접 선언
typedef CK_RV (*CA_EncapsulateKeyFunc)(CK_SESSION_HANDLE, CK_MECHANISM_PTR, CK_OBJECT_HANDLE,
                                        CK_ATTRIBUTE_PTR, CK_ULONG, CK_BYTE_PTR, CK_ULONG_PTR,
                                        CK_OBJECT_HANDLE_PTR);
typedef CK_RV (*CA_DecapsulateKeyFunc)(CK_SESSION_HANDLE, CK_MECHANISM_PTR, CK_OBJECT_HANDLE,
                                        CK_ATTRIBUTE_PTR, CK_ULONG, CK_BYTE_PTR, CK_ULONG,
                                        CK_OBJECT_HANDLE_PTR);

int main() {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    CK_SLOT_ID slotId = 0;
    CK_UTF8CHAR_PTR pin = (CK_UTF8CHAR_PTR)"{partition-password}";

    const char *libPath = getenv("SfntLibPath");
    void *libHandle = dlopen(libPath, RTLD_NOW);
    CK_C_GetFunctionList C_GetFunctionList = (CK_C_GetFunctionList)dlsym(libHandle, "C_GetFunctionList");
    CK_FUNCTION_LIST_PTR p11 = NULL;
    CK_RV rv = C_GetFunctionList(&p11);

    CA_EncapsulateKeyFunc CA_EncapsulateKey = (CA_EncapsulateKeyFunc)dlsym(libHandle, "CA_EncapsulateKey");
    CA_DecapsulateKeyFunc CA_DecapsulateKey = (CA_DecapsulateKeyFunc)dlsym(libHandle, "CA_DecapsulateKey");

    CK_SESSION_HANDLE hSession;
    p11->C_Initialize(NULL_PTR);
    p11->C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
    rv = p11->C_Login(hSession, CKU_USER, pin, (CK_ULONG)strlen((char *)pin));
    if (rv != CKR_OK) {
        fprintf(stderr, "C_Login failed: 0x%lx\n", rv);
        return 1;
    }

    // --- ML-KEM 키 쌍 생성 ---
    CK_ULONG parameterSet = CKP_ML_KEM_768;  // 512/768/1024 중 선택
    CK_BBOOL ckTrue = CK_TRUE;

    // 공개키는 캡슐화(Encapsulate)용, 개인키는 역캡슐화(Decapsulate)용으로 속성을 지정
    CK_ATTRIBUTE publicKeyTemplate[] = {
        { CKA_TOKEN,         &ckTrue,       sizeof(ckTrue) },
        { CKA_PRIVATE,       &ckTrue,       sizeof(ckTrue) },
        { CKA_ENCAPSULATE,   &ckTrue,       sizeof(ckTrue) },
        { CKA_PARAMETER_SET, &parameterSet, sizeof(parameterSet) },
    };
    CK_ATTRIBUTE privateKeyTemplate[] = {
        { CKA_TOKEN,         &ckTrue,       sizeof(ckTrue) },
        { CKA_PRIVATE,       &ckTrue,       sizeof(ckTrue) },
        { CKA_SENSITIVE,     &ckTrue,       sizeof(ckTrue) },
        { CKA_DECAPSULATE,   &ckTrue,       sizeof(ckTrue) },
        { CKA_PARAMETER_SET, &parameterSet, sizeof(parameterSet) },
    };

    CK_MECHANISM keyGenMechanism = { CKM_ML_KEM_KEY_PAIR_GEN, NULL_PTR, 0 };
    CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
    rv = p11->C_GenerateKeyPair(hSession, &keyGenMechanism,
                                 publicKeyTemplate, sizeof(publicKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 privateKeyTemplate, sizeof(privateKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 &hPublicKey, &hPrivateKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_GenerateKeyPair failed: 0x%lx\n", rv);
        return 1;
    }

    // --- 캡슐화: 공개키로 공유 비밀(AES 키)을 생성하고 암호문을 얻는다 ---
    CK_MECHANISM kemMechanism = { CKM_ML_KEM, NULL_PTR, 0 };

    // 캡슐화로 만들어질 공유 비밀(AES-256) 키 객체의 속성
    CK_OBJECT_CLASS secretClass = CKO_SECRET_KEY;
    CK_KEY_TYPE aesKeyType = CKK_AES;
    CK_ULONG keyLen = 32;
    CK_ATTRIBUTE secretKeyTemplate[] = {
        { CKA_CLASS,      &secretClass, sizeof(secretClass) },
        { CKA_KEY_TYPE,   &aesKeyType,  sizeof(aesKeyType) },
        { CKA_VALUE_LEN,  &keyLen,      sizeof(keyLen) },
        { CKA_TOKEN,      &ckTrue,      sizeof(ckTrue) },
        { CKA_SENSITIVE,  &ckTrue,      sizeof(ckTrue) },
        { CKA_ENCRYPT,    &ckTrue,      sizeof(ckTrue) },
        { CKA_DECRYPT,    &ckTrue,      sizeof(ckTrue) },
    };
    CK_ULONG secretKeyTemplateCount = sizeof(secretKeyTemplate) / sizeof(CK_ATTRIBUTE);

    // ML-KEM 암호문은 파라미터 세트별로 크기가 고정되어 있으므로(FIPS 203, ML-KEM-768은 1088바이트),
    // C_Sign처럼 먼저 길이를 조회하는 대신 넉넉한 고정 버퍼를 미리 준비해 한 번에 호출한다.
    CK_ULONG ciphertextLen = 4096;
    CK_BYTE_PTR ciphertext = (CK_BYTE_PTR)malloc(ciphertextLen);
    CK_OBJECT_HANDLE hSenderSecret;
    rv = CA_EncapsulateKey(hSession, &kemMechanism, hPublicKey,
                            secretKeyTemplate, secretKeyTemplateCount,
                            ciphertext, &ciphertextLen, &hSenderSecret);
    if (rv != CKR_OK) {
        fprintf(stderr, "CA_EncapsulateKey failed: 0x%lx\n", rv);
        free(ciphertext);
        return 1;
    }
    printf("Encapsulated (ciphertext %lu bytes, shared secret handle=%lu)\n",
           (unsigned long)ciphertextLen, (unsigned long)hSenderSecret);

    // --- 역캡슐화: 개인키와 암호문으로 동일한 공유 비밀을 복원한다 ---
    CK_OBJECT_HANDLE hReceiverSecret;
    rv = CA_DecapsulateKey(hSession, &kemMechanism, hPrivateKey,
                            secretKeyTemplate, secretKeyTemplateCount,
                            ciphertext, ciphertextLen, &hReceiverSecret);
    if (rv != CKR_OK) {
        fprintf(stderr, "CA_DecapsulateKey failed: 0x%lx\n", rv);
        free(ciphertext);
        return 1;
    }
    printf("Decapsulated (shared secret handle=%lu)\n", (unsigned long)hReceiverSecret);

    // --- 공유 비밀 일치 여부 검증: 한쪽 키로 암호화하고 다른 쪽 키로 복호화해서 원문이 복원되는지 확인 ---
    CK_BYTE iv[16];
    p11->C_GenerateRandom(hSession, iv, sizeof(iv));
    CK_MECHANISM aesMechanism = { CKM_AES_CBC_PAD, iv, sizeof(iv) };

    CK_BYTE plaintext[] = "PQC KEM test data";
    CK_ULONG plaintextLen = sizeof(plaintext) - 1;

    // 송신측 공유 비밀(hSenderSecret)로 암호화
    p11->C_EncryptInit(hSession, &aesMechanism, hSenderSecret);
    CK_ULONG encryptedLen = 0;
    p11->C_Encrypt(hSession, plaintext, plaintextLen, NULL_PTR, &encryptedLen);
    CK_BYTE_PTR encrypted = (CK_BYTE_PTR)malloc(encryptedLen);
    p11->C_Encrypt(hSession, plaintext, plaintextLen, encrypted, &encryptedLen);

    // 수신측 공유 비밀(hReceiverSecret)로 복호화
    p11->C_DecryptInit(hSession, &aesMechanism, hReceiverSecret);
    CK_ULONG decryptedLen = 0;
    p11->C_Decrypt(hSession, encrypted, encryptedLen, NULL_PTR, &decryptedLen);
    CK_BYTE_PTR decrypted = (CK_BYTE_PTR)malloc(decryptedLen);
    p11->C_Decrypt(hSession, encrypted, encryptedLen, decrypted, &decryptedLen);

    bool matches = (decryptedLen == plaintextLen) && (memcmp(plaintext, decrypted, plaintextLen) == 0);
    printf("Shared secret matches: %s\n", matches ? "true" : "false");

    free(encrypted);
    free(decrypted);
    free(ciphertext);
    p11->C_Logout(hSession);
    p11->C_CloseSession(hSession);
    p11->C_Finalize(NULL_PTR);
    dlclose(libHandle);
    return 0;
}
```

[빌드 방법](/docs/hsm-vpc-pqc-cpp#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)에 따라 `mlkem_example`로 빌드한 뒤 실행하십시오.

```
export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so
./mlkem_example
```

다음과 같은 결과가 출력되면 정상입니다(객체 핸들 값은 세션마다 다를 수 있습니다).

```
Encapsulated (ciphertext 1088 bytes, shared secret handle=6)
Decapsulated (shared secret handle=7)
Shared secret matches: true
```

## ML-DSA 키 생성, 서명 및 검증

ML-DSA 키 쌍은 `CKM_ML_DSA_KEY_PAIR_GEN` mechanism으로 생성하고, `CKA_PARAMETER_SET` attribute로 파라미터 세트(44/65/87)를 지정합니다. 서명·검증은 `CKM_ML_DSA` mechanism을 사용합니다. `mldsa_signature_example.cpp` 파일을 아래 내용으로 생성하십시오.

```
#include "cryptoki_v2.h"
#include <dlfcn.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>

int main() {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    CK_SLOT_ID slotId = 0;
    CK_UTF8CHAR_PTR pin = (CK_UTF8CHAR_PTR)"{partition-password}";

    const char *libPath = getenv("SfntLibPath");
    void *libHandle = dlopen(libPath, RTLD_NOW);
    CK_C_GetFunctionList C_GetFunctionList = (CK_C_GetFunctionList)dlsym(libHandle, "C_GetFunctionList");
    CK_FUNCTION_LIST_PTR p11 = NULL;
    CK_RV rv = C_GetFunctionList(&p11);

    CK_SESSION_HANDLE hSession;
    p11->C_Initialize(NULL_PTR);
    p11->C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
    rv = p11->C_Login(hSession, CKU_USER, pin, (CK_ULONG)strlen((char *)pin));
    if (rv != CKR_OK) {
        fprintf(stderr, "C_Login failed: 0x%lx\n", rv);
        return 1;
    }

    // --- ML-DSA 키 쌍 생성 ---
    CK_ULONG parameterSet = CKP_ML_DSA_65;  // ML-DSA-65 (44/65/87 중 선택)
    CK_BBOOL ckTrue = CK_TRUE;

    CK_ATTRIBUTE publicKeyTemplate[] = {
        { CKA_TOKEN,          &ckTrue,       sizeof(ckTrue) },
        { CKA_PRIVATE,        &ckTrue,       sizeof(ckTrue) },
        { CKA_VERIFY,         &ckTrue,       sizeof(ckTrue) },
        { CKA_PARAMETER_SET,  &parameterSet, sizeof(parameterSet) },
    };
    CK_ATTRIBUTE privateKeyTemplate[] = {
        { CKA_TOKEN,          &ckTrue,       sizeof(ckTrue) },
        { CKA_PRIVATE,        &ckTrue,       sizeof(ckTrue) },
        { CKA_SENSITIVE,      &ckTrue,       sizeof(ckTrue) },
        { CKA_SIGN,           &ckTrue,       sizeof(ckTrue) },
        { CKA_PARAMETER_SET,  &parameterSet, sizeof(parameterSet) },
    };

    CK_MECHANISM keyGenMechanism = { CKM_ML_DSA_KEY_PAIR_GEN, NULL_PTR, 0 };
    CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
    rv = p11->C_GenerateKeyPair(hSession, &keyGenMechanism,
                                 publicKeyTemplate, sizeof(publicKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 privateKeyTemplate, sizeof(privateKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 &hPublicKey, &hPrivateKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_GenerateKeyPair failed: 0x%lx\n", rv);
        return 1;
    }

    // --- 서명 ---
    CK_MECHANISM signMechanism = { CKM_ML_DSA, NULL_PTR, 0 };
    CK_BYTE message[] = "PQC ML-DSA signature test";
    CK_ULONG messageLen = sizeof(message) - 1;

    rv = p11->C_SignInit(hSession, &signMechanism, hPrivateKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_SignInit failed: 0x%lx\n", rv);
        return 1;
    }

    CK_ULONG signatureLen = 0;
    p11->C_Sign(hSession, message, messageLen, NULL_PTR, &signatureLen);

    CK_BYTE_PTR signature = (CK_BYTE_PTR)malloc(signatureLen);
    rv = p11->C_Sign(hSession, message, messageLen, signature, &signatureLen);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_Sign failed: 0x%lx\n", rv);
        free(signature);
        return 1;
    }
    printf("Signature generated (%lu bytes)\n", (unsigned long)signatureLen);

    // --- 검증 ---
    CK_MECHANISM verifyMechanism = { CKM_ML_DSA, NULL_PTR, 0 };
    rv = p11->C_VerifyInit(hSession, &verifyMechanism, hPublicKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_VerifyInit failed: 0x%lx\n", rv);
        free(signature);
        return 1;
    }

    rv = p11->C_Verify(hSession, message, messageLen, signature, signatureLen);
    if (rv == CKR_OK) {
        printf("Signature verification succeeded\n");
    } else if (rv == CKR_SIGNATURE_INVALID) {
        printf("Signature verification failed - signature is invalid\n");
    } else {
        fprintf(stderr, "C_Verify failed: 0x%lx\n", rv);
    }

    free(signature);
    p11->C_Logout(hSession);
    p11->C_CloseSession(hSession);
    p11->C_Finalize(NULL_PTR);
    dlclose(libHandle);
    return 0;
}
```

[빌드 방법](/docs/hsm-vpc-pqc-cpp#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)에 따라 `mldsa_signature_example`로 빌드한 뒤 실행하십시오.

```
export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so
./mldsa_signature_example
```

다음과 같은 결과가 출력되면 정상입니다. 서명 길이는 파라미터 세트별로 고정되어 있으며(ML-DSA-65는 FIPS 204 기준 3309바이트), 이 값이 그대로 출력됩니다.

```
Signature generated (3309 bytes)
Signature verification succeeded
```

## HSS 키 생성

HSS 키 쌍은 `CKM_HSS_KEY_PAIR_GEN` mechanism으로 생성합니다. `hss_keygen_example.cpp` 파일을 아래 내용으로 생성하십시오.

```
#include "cryptoki_v2.h"
#include <dlfcn.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>

int main() {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    CK_SLOT_ID slotId = 0;
    CK_UTF8CHAR_PTR pin = (CK_UTF8CHAR_PTR)"{partition-password}";

    // Cryptoki 라이브러리 로드 및 함수 테이블 획득
    const char *libPath = getenv("SfntLibPath");
    void *libHandle = dlopen(libPath, RTLD_NOW);
    CK_C_GetFunctionList C_GetFunctionList = (CK_C_GetFunctionList)dlsym(libHandle, "C_GetFunctionList");
    CK_FUNCTION_LIST_PTR p11 = NULL;
    CK_RV rv = C_GetFunctionList(&p11);

    // 세션 오픈 및 파티션 로그인
    CK_SESSION_HANDLE hSession;
    p11->C_Initialize(NULL_PTR);
    p11->C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
    rv = p11->C_Login(hSession, CKU_USER, pin, (CK_ULONG)strlen((char *)pin));
    if (rv != CKR_OK) {
        fprintf(stderr, "C_Login failed: 0x%lx\n", rv);
        return 1;
    }

    // HSS 파라미터: 레벨 수 1, LMS_SHA256_M32_H10 / LMOTS_SHA256_N32_W4 (IANA 등록 표준값)
    CK_ULONG hssLevels = 1;
    CK_ULONG lmsType   = 0x00000006UL;  // LMS_SHA256_M32_H10
    CK_ULONG lmotsType = 0x00000003UL;  // LMOTS_SHA256_N32_W4
    CK_BBOOL ckTrue = CK_TRUE;

    // 공개키 템플릿에는 HSS 관련 속성을 넣지 않는다
    CK_ATTRIBUTE publicKeyTemplate[] = {
        { CKA_TOKEN,   &ckTrue, sizeof(ckTrue) },
        { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) },
        { CKA_VERIFY,  &ckTrue, sizeof(ckTrue) },
    };

    // HSS 관련 속성(복수형: LMS_TYPES/LMOTS_TYPES)은 개인키 템플릿에만 지정한다
    CK_ATTRIBUTE privateKeyTemplate[] = {
        { CKA_TOKEN,           &ckTrue,    sizeof(ckTrue) },
        { CKA_PRIVATE,         &ckTrue,    sizeof(ckTrue) },
        { CKA_SENSITIVE,       &ckTrue,    sizeof(ckTrue) },
        { CKA_SIGN,            &ckTrue,    sizeof(ckTrue) },
        { CKA_HSS_LEVELS,      &hssLevels, sizeof(hssLevels) },
        { CKA_HSS_LMS_TYPES,   &lmsType,   sizeof(lmsType) },
        { CKA_HSS_LMOTS_TYPES, &lmotsType, sizeof(lmotsType) },
    };

    CK_MECHANISM keyGenMechanism = { CKM_HSS_KEY_PAIR_GEN, NULL_PTR, 0 };
    CK_OBJECT_HANDLE hPublicKey, hPrivateKey;

    rv = p11->C_GenerateKeyPair(hSession, &keyGenMechanism,
                                 publicKeyTemplate, sizeof(publicKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 privateKeyTemplate, sizeof(privateKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 &hPublicKey, &hPrivateKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_GenerateKeyPair failed: 0x%lx\n", rv);
        return 1;
    }
    printf("HSS key pair generated (public=%lu, private=%lu)\n", (unsigned long)hPublicKey, (unsigned long)hPrivateKey);

    p11->C_Logout(hSession);
    p11->C_CloseSession(hSession);
    p11->C_Finalize(NULL_PTR);
    dlclose(libHandle);
    return 0;
}
```

[빌드 방법](/docs/hsm-vpc-pqc-cpp#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)에 따라 `hss_keygen_example`로 빌드한 뒤 실행하십시오.

```
export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so
./hss_keygen_example
```

다음과 같은 결과가 출력되면 정상입니다(객체 핸들 값은 세션마다 다를 수 있습니다).

```
HSS key pair generated (public=3, private=4)
```

## HSS 서명 생성 및 검증

서명·검증은 `CKM_HSS` mechanism을 사용합니다. `hss_signature_example.cpp` 파일을 아래 내용으로 생성하십시오 (키 생성부터 서명·검증까지 하나의 프로그램으로 실행됩니다).

```
#include "cryptoki_v2.h"
#include <dlfcn.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>

int main() {
    // 실제 파티션의 슬롯 번호와 비밀번호로 교체하십시오.
    CK_SLOT_ID slotId = 0;
    CK_UTF8CHAR_PTR pin = (CK_UTF8CHAR_PTR)"{partition-password}";

    const char *libPath = getenv("SfntLibPath");
    void *libHandle = dlopen(libPath, RTLD_NOW);
    CK_C_GetFunctionList C_GetFunctionList = (CK_C_GetFunctionList)dlsym(libHandle, "C_GetFunctionList");
    CK_FUNCTION_LIST_PTR p11 = NULL;
    CK_RV rv = C_GetFunctionList(&p11);

    CK_SESSION_HANDLE hSession;
    p11->C_Initialize(NULL_PTR);
    p11->C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
    rv = p11->C_Login(hSession, CKU_USER, pin, (CK_ULONG)strlen((char *)pin));
    if (rv != CKR_OK) {
        fprintf(stderr, "C_Login failed: 0x%lx\n", rv);
        return 1;
    }

    // --- HSS 키 쌍 생성 (자세한 설명은 "HSS 키 생성" 절 참고) ---
    CK_ULONG hssLevels = 1;
    CK_ULONG lmsType   = 0x00000006UL;  // LMS_SHA256_M32_H10
    CK_ULONG lmotsType = 0x00000003UL;  // LMOTS_SHA256_N32_W4
    CK_BBOOL ckTrue = CK_TRUE;

    CK_ATTRIBUTE publicKeyTemplate[] = {
        { CKA_TOKEN,   &ckTrue, sizeof(ckTrue) },
        { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) },
        { CKA_VERIFY,  &ckTrue, sizeof(ckTrue) },
    };
    CK_ATTRIBUTE privateKeyTemplate[] = {
        { CKA_TOKEN,           &ckTrue,    sizeof(ckTrue) },
        { CKA_PRIVATE,         &ckTrue,    sizeof(ckTrue) },
        { CKA_SENSITIVE,       &ckTrue,    sizeof(ckTrue) },
        { CKA_SIGN,            &ckTrue,    sizeof(ckTrue) },
        { CKA_HSS_LEVELS,      &hssLevels, sizeof(hssLevels) },
        { CKA_HSS_LMS_TYPES,   &lmsType,   sizeof(lmsType) },
        { CKA_HSS_LMOTS_TYPES, &lmotsType, sizeof(lmotsType) },
    };

    CK_MECHANISM keyGenMechanism = { CKM_HSS_KEY_PAIR_GEN, NULL_PTR, 0 };
    CK_OBJECT_HANDLE hPublicKey, hPrivateKey;
    rv = p11->C_GenerateKeyPair(hSession, &keyGenMechanism,
                                 publicKeyTemplate, sizeof(publicKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 privateKeyTemplate, sizeof(privateKeyTemplate) / sizeof(CK_ATTRIBUTE),
                                 &hPublicKey, &hPrivateKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_GenerateKeyPair failed: 0x%lx\n", rv);
        return 1;
    }

    // --- 서명 ---
    CK_MECHANISM signMechanism = { CKM_HSS, NULL_PTR, 0 };
    CK_BYTE message[] = "PQC HSS signature test";
    CK_ULONG messageLen = sizeof(message) - 1;

    rv = p11->C_SignInit(hSession, &signMechanism, hPrivateKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_SignInit failed: 0x%lx\n", rv);
        return 1;
    }

    // 먼저 길이 0으로 호출해서 필요한 서명 버퍼 크기를 조회
    CK_ULONG signatureLen = 0;
    p11->C_Sign(hSession, message, messageLen, NULL_PTR, &signatureLen);

    CK_BYTE_PTR signature = (CK_BYTE_PTR)malloc(signatureLen);
    rv = p11->C_Sign(hSession, message, messageLen, signature, &signatureLen);
    if (rv != CKR_OK) {
        if (rv == CKR_KEY_EXHAUSTED) {
            // HSS는 상태 유지형(stateful) 서명 방식이라 키마다 서명 가능 횟수가 정해져 있음
            // (자세한 내용은 "상태 유지 제약" 절 참고)
            fprintf(stderr, "C_Sign failed: CKR_KEY_EXHAUSTED - HSS one-time key exhausted\n");
        } else {
            fprintf(stderr, "C_Sign failed: 0x%lx\n", rv);
        }
        free(signature);
        return 1;
    }
    printf("Signature generated (%lu bytes)\n", (unsigned long)signatureLen);

    // --- 검증 ---
    CK_MECHANISM verifyMechanism = { CKM_HSS, NULL_PTR, 0 };
    rv = p11->C_VerifyInit(hSession, &verifyMechanism, hPublicKey);
    if (rv != CKR_OK) {
        fprintf(stderr, "C_VerifyInit failed: 0x%lx\n", rv);
        free(signature);
        return 1;
    }

    rv = p11->C_Verify(hSession, message, messageLen, signature, signatureLen);
    if (rv == CKR_OK) {
        printf("Signature verification succeeded\n");
    } else if (rv == CKR_SIGNATURE_INVALID) {
        printf("Signature verification failed - signature is invalid\n");
    } else {
        fprintf(stderr, "C_Verify failed: 0x%lx\n", rv);
    }

    free(signature);
    p11->C_Logout(hSession);
    p11->C_CloseSession(hSession);
    p11->C_Finalize(NULL_PTR);
    dlclose(libHandle);
    return 0;
}
```

[빌드 방법](/docs/hsm-vpc-pqc-cpp#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)에 따라 `hss_signature_example`로 빌드한 뒤 실행하십시오.

```
export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so
./hss_signature_example
```

다음과 같은 결과가 출력되면 정상입니다.

```
Signature generated (2512 bytes)
Signature verification succeeded
```

## 상태 유지(state) 제약 — 반드시 확인하십시오

주의

HSS/LMS는 상태를 유지하는(stateful) 서명 방식입니다. 개인키 자체가 "남은 서명 가능 횟수"를 내장하고 매 서명마다 소진되며, 이 때문에 아래와 같은 근본적인 제약이 있습니다.

- **개인키 복제(clone)가 불가능합니다.** 그 결과로 **백업/복원도 불가능하고, HA(고가용성) 그룹에도 포함시킬 수 없습니다.** Thales 공식 문서: "HSS private keys cannot be cloned (therefore, no backup/restore, no inclusion in HA groups)." / "Do not add a partition to an HA group if the partition has an HSS private key on it."
- **서명 가능 횟수가 소진되면 `CKR_KEY_EXHAUSTED`가 반환됩니다.** `CKA_HSS_KEYS_REMAINING` attribute로 남은 서명 가능 횟수를 조회할 수 있습니다.
- **백업 스크립트나 HA 파티션 구성에 HSS 개인키를 포함시키지 마십시오.** NIST가 승인한 설계상의 제약이라 우회할 방법이 없습니다.

HA가 필요한 워크로드라면 HSS/LMS 대신 ML-DSA(격자 기반, stateless) 사용을 검토하십시오.

## 문제 해결

### `C_Login failed: 0x...` (`CKR_PIN_INCORRECT` 등)

`pin` 변수를 실제 파티션 비밀번호로 바꾸지 않았거나, 값이 틀린 경우 발생합니다. `{partition-password}` placeholder를 실제 값으로 교체하십시오.

### `dlopen(...) failed` 또는 컴파일 시 헤더를 찾을 수 없는 경우

LunaClient가 설치되어 있지 않거나, [빌드 방법](/docs/hsm-vpc-pqc-cpp#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)의 경로가 실제 설치 경로와 다른 경우 발생합니다. LunaClient 10.9.3 설치 여부를 확인하십시오.

### 세션은 열리는데 로그인 이후 응답이 없거나 연결 오류가 나는 경우

HSM 클라이언트 서버와 HSM 파티션 간의 네트워크 연결(Connection)이나 파티션 초기 설정이 완료되지 않은 경우 발생할 수 있습니다. [전제 조건](/docs/hsm-vpc-pqc-cpp#%EC%A0%84%EC%A0%9C%EC%A1%B0%EA%B1%B4)의 HSM 연결 생성·파티션 초기 설정이 완료됐는지 확인하십시오.
