--- title: "C++ 연동" slug: "cpp-quickstart-guide" updated: 2026-10-22T09:08:02Z published: 2026-10-22T09:08:02Z canonical: "guide-gov.ncloud-docs.com/cpp-quickstart-guide" --- > ## 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. # C++ 연동 Hardware Security Module(HSM) 상품은 Thales Luna HSM을 기반으로 하며, PKCS#11(Cryptoki) C/C++ API로 사용할 수 있습니다. 이 문서에서는 C++ 애플리케이션에서 Cryptoki API로 HSM 파티션에 접속해 AES/RSA 같은 기본 알고리즘으로 키 생성, 암호화·복호화, 서명·검증을 수행하는 최소 예제를 안내합니다. 참고 CLI로 먼저 만져보려면 [CLI 도구 활용 (lunacm/ckdemo/multitoken)](/docs/hsm-cli-tools-guide)를 참고해 주십시오. LunaClient를 설치하면 `p11Sample`(`ckSample` 패키지) 소스 예제도 함께 설치됩니다(보통 `/usr/safenet/lunaclient/sdk/samples/` 아래). 이 문서의 `dlopen`/`dlsym` 패턴도 이 샘플을 기반으로 하며, 더 다양한 예제가 필요하면 먼저 이 샘플을 찾아보십시오. ## 지원 환경 지원 환경을 설명합니다. ### 전제 조건 연동을 시작하기 전, 다음의 전제 조건을 준수해 주십시오. - 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 aes_example aes_example.cpp \ -ldl ``` 다른 예제도 소스 파일명만 바꾸면 동일합니다. ## 세션 초기화 및 파티션 로그인 아래는 모든 예제 코드에 공통으로 포함되는 부분입니다. Cryptoki 라이브러리를 `dlopen`으로 불러오고, 세션을 열어 파티션에 로그인합니다. ``` #include "cryptoki_v2.h" #include #include #include #include // 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 ``` 슬롯 번호는 `lunacm:> slot list`로 확인할 수 있습니다. ([CLI 도구 활용](/docs/hsm-cli-tools-guide) 참고) ## AES 키 생성 및 암호화·복호화 AES 키는 `CKM_AES_KEY_GEN` mechanism으로 생성하고, 암호화·복호화는 `CKM_AES_CBC_PAD` mechanism을 사용합니다. `aes_example.cpp` 파일을 아래 내용으로 생성하십시오. ``` #include "cryptoki_v2.h" #include #include #include #include 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; } // --- AES-256 키 생성 (CKA_TOKEN을 지정하지 않아 기본값(CK_FALSE)인 세션 객체로 생성됨) --- CK_BBOOL ckTrue = CK_TRUE; CK_ULONG keyLen = 32; CK_ATTRIBUTE aesKeyTemplate[] = { { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, }; CK_MECHANISM keyGenMechanism = { CKM_AES_KEY_GEN, NULL_PTR, 0 }; CK_OBJECT_HANDLE hAesKey; rv = p11->C_GenerateKey(hSession, &keyGenMechanism, aesKeyTemplate, sizeof(aesKeyTemplate) / sizeof(CK_ATTRIBUTE), &hAesKey); if (rv != CKR_OK) { fprintf(stderr, "C_GenerateKey failed: 0x%lx\n", rv); return 1; } // --- 암호화 --- CK_BYTE iv[16]; p11->C_GenerateRandom(hSession, iv, sizeof(iv)); CK_MECHANISM aesMechanism = { CKM_AES_CBC_PAD, iv, sizeof(iv) }; CK_BYTE plaintext[] = "HSM C++ Quickstart test data"; CK_ULONG plaintextLen = sizeof(plaintext) - 1; p11->C_EncryptInit(hSession, &aesMechanism, hAesKey); CK_ULONG encryptedLen = 0; p11->C_Encrypt(hSession, plaintext, plaintextLen, NULL_PTR, &encryptedLen); CK_BYTE_PTR encrypted = (CK_BYTE_PTR)malloc(encryptedLen); rv = p11->C_Encrypt(hSession, plaintext, plaintextLen, encrypted, &encryptedLen); if (rv != CKR_OK) { fprintf(stderr, "C_Encrypt failed: 0x%lx\n", rv); free(encrypted); return 1; } // --- 복호화 (같은 IV로 재초기화) --- p11->C_DecryptInit(hSession, &aesMechanism, hAesKey); CK_ULONG decryptedLen = 0; p11->C_Decrypt(hSession, encrypted, encryptedLen, NULL_PTR, &decryptedLen); CK_BYTE_PTR decrypted = (CK_BYTE_PTR)malloc(decryptedLen); rv = p11->C_Decrypt(hSession, encrypted, encryptedLen, decrypted, &decryptedLen); if (rv != CKR_OK) { fprintf(stderr, "C_Decrypt failed: 0x%lx\n", rv); free(encrypted); free(decrypted); return 1; } bool matches = (decryptedLen == plaintextLen) && (memcmp(plaintext, decrypted, plaintextLen) == 0); printf("Decryption matches original: %s\n", matches ? "true" : "false"); free(encrypted); free(decrypted); p11->C_Logout(hSession); p11->C_CloseSession(hSession); p11->C_Finalize(NULL_PTR); dlclose(libHandle); return 0; } ``` [빌드 방법](/docs/cpp-quickstart-guide#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)에 따라 `aes_example`로 빌드한 뒤 실행하십시오. ``` export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so ./aes_example ``` 다음과 같은 결과가 출력되면 정상입니다. ``` Decryption matches original: true ``` ### 생성한 키를 다음 실행에서도 다시 쓰려면 위 예제처럼 `CKA_TOKEN`을 지정하지 않고 생성한 키는 **세션(session) 객체**입니다(생략 시 기본값은 `CK_FALSE`). 세션(프로그램)이 끝나면 HSM에서도 함께 사라지므로, 다음번 실행에서 같은 키를 다시 쓸 수 없습니다. 같은 키를 다음 실행에서도 찾아 쓰려면, 생성 시점에 `CKA_TOKEN = CK_TRUE`와 나중에 찾을 수 있는 `CKA_LABEL`을 템플릿에 추가해 영구(token) 객체로 만들면 됩니다. Java의 `KeyStore.setKeyEntry` 같은 별도 API 호출은 필요 없습니다. ``` CK_BBOOL ckTrue = CK_TRUE; CK_UTF8CHAR label[] = "my-aes-key"; CK_ULONG keyLen = 32; CK_ATTRIBUTE aesKeyTemplate[] = { { CKA_TOKEN, &ckTrue, sizeof(ckTrue) }, // 영구(token) 객체로 생성 { CKA_LABEL, label, sizeof(label) - 1 }, // 나중에 이 label로 찾음 { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, { CKA_ENCRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_DECRYPT, &ckTrue, sizeof(ckTrue) }, { CKA_VALUE_LEN, &keyLen, sizeof(keyLen) }, }; ``` 다음 실행(또는 다른 프로세스)에서는 새로 생성하지 않고, `CKA_LABEL`로 검색(`C_FindObjectsInit`/`C_FindObjects`)해서 같은 키의 핸들을 다시 얻습니다. ``` CK_UTF8CHAR label[] = "my-aes-key"; CK_ATTRIBUTE findTemplate[] = { { CKA_LABEL, label, sizeof(label) - 1 }, }; CK_OBJECT_HANDLE hAesKey; CK_ULONG foundCount = 0; p11->C_FindObjectsInit(hSession, findTemplate, 1); p11->C_FindObjects(hSession, &hAesKey, 1, &foundCount); p11->C_FindObjectsFinal(hSession); if (foundCount == 0) { fprintf(stderr, "Key not found\n"); return 1; } ``` 참고 `CKA_TOKEN = CK_TRUE`로 만든 키는 프로그램이 끝나도 파티션에 남습니다. 테스트하며 여러 번 만들었다면 [ckdemo](/docs/hsm-cli-tools-guide)의 Object Management 메뉴(`22`, Destroy object)로 정리해 주십시오. ## RSA 키 생성, 서명 및 검증 RSA 키쌍은 `CKM_RSA_PKCS_KEY_PAIR_GEN` mechanism으로 생성하고, 서명·검증은 `CKM_SHA256_RSA_PKCS` mechanism을 사용합니다. `rsa_signature_example.cpp` 파일을 아래 내용으로 생성하십시오. ``` #include "cryptoki_v2.h" #include #include #include #include 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; } // --- RSA-2048 키쌍 생성 (CKA_TOKEN을 지정하지 않아 기본값(CK_FALSE)인 세션 객체로 생성됨) --- CK_BBOOL ckTrue = CK_TRUE; CK_ULONG modulusBits = 2048; CK_BYTE publicExponent[] = { 0x01, 0x00, 0x01 }; // 65537 CK_ATTRIBUTE publicKeyTemplate[] = { { CKA_VERIFY, &ckTrue, sizeof(ckTrue) }, { CKA_MODULUS_BITS, &modulusBits, sizeof(modulusBits) }, { CKA_PUBLIC_EXPONENT, publicExponent, sizeof(publicExponent) }, }; CK_ATTRIBUTE privateKeyTemplate[] = { { CKA_PRIVATE, &ckTrue, sizeof(ckTrue) }, { CKA_SENSITIVE, &ckTrue, sizeof(ckTrue) }, { CKA_SIGN, &ckTrue, sizeof(ckTrue) }, }; CK_MECHANISM keyGenMechanism = { CKM_RSA_PKCS_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_SHA256_RSA_PKCS, NULL_PTR, 0 }; CK_BYTE message[] = "HSM C++ Quickstart 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_SHA256_RSA_PKCS, 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/cpp-quickstart-guide#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)에 따라 `rsa_signature_example`로 빌드한 뒤 실행하십시오. ``` export SfntLibPath=/usr/safenet/lunaclient/lib/libCryptoki2_64.so ./rsa_signature_example ``` 다음과 같은 결과가 출력되면 정상입니다(RSA-2048 서명은 256바이트 고정 길이). ``` Signature generated (256 bytes) Signature verification succeeded ``` ## 문제 해결 ### 로그인 실패 `C_Login failed: 0x...` (`CKR_PIN_INCORRECT` 등) 오류가 발생합니다. #### 원인 `pin` 변수를 실제 파티션 비밀번호로 바꾸지 않았거나, 값이 틀린 경우 발생합니다. `{partition-password}` placeholder를 실제 값으로 교체하십시오. ### dlopen 실패 `dlopen(...) failed` 또는 컴파일 시 헤더를 찾을 수 없습니다. #### 원인 LunaClient가 설치되어 있지 않거나, [빌드 방법](/docs/cpp-quickstart-guide#%EB%B9%8C%EB%93%9C%EB%B0%A9%EB%B2%95)의 경로가 실제 설치 경로와 다른 경우 발생합니다. #### 해결 방법 LunaClient 10.9.3 설치 여부를 확인하십시오. ### 연결 오류 세션은 열리는데 로그인 이후 응답이 없거나 연결 오류가 발생합니다. #### 원인 HSM 클라이언트 서버와 HSM 파티션 간의 네트워크 연결(Connection)이나 파티션 초기 설정이 완료되지 않은 경우 발생할 수 있습니다. #### 해결 방법 [전제 조건](/docs/cpp-quickstart-guide#%EC%A0%84%EC%A0%9C%EC%A1%B0%EA%B1%B4)의 HSM 커넥션 생성·파티션 초기 설정이 완료됐는지 확인하십시오. ## 다음 단계 - CLI 확인: [CLI 도구 활용 (lunacm/ckdemo/multitoken)](/docs/hsm-cli-tools-guide) - Java 사용: [Java 연동 Quickstart](/docs/java-quickstart-guide)