비고
일부 정보는 상용 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적이거나 묵시적인 보증도 하지 않습니다. 이 항목에 설명된 기능은 Windows Insider Preview시험판 버전에서 사용할 수 있습니다.
이 문서에서는 Microsoft의 CNG API와 함께 ML-DSA 알고리즘을 사용하여 디지털 서명을 만들고 확인하기 위한 엔드 투 엔드 워크플로를 구현하는 가이드를 제공합니다.
BCrypt를 사용하는 샘플 ML-DSA 코드
디지털 서명을 위해 Microsoft의 CNG API와 함께 ML-DSA 알고리즘을 사용하는 방법을 알아봅니다. BCrypt 샘플에는 메시지에 서명하고 서명을 확인하고 공개 키를 내보내는 함수가 포함되어 있습니다. 이 코드는 쉽게 따르고 이해할 수 있도록 설계되어 애플리케이션에서 양자 후 디지털 서명을 구현하려는 개발자에게 적합합니다.
ML-DSA 사용하여 서명 생성 및 확인
이 코드 샘플에서는 Microsoft의 CNG API에서 ML-DSA 알고리즘을 사용하여 디지털 서명을 만들고 확인하기 위한 엔드 투 엔드 워크플로를 보여 줍니다. 서명 및 확인 중에 컨텍스트 일치의 중요성과 암호화 핸들 및 메모리를 신중하게 관리하는 것이 중요합니다. 양자 후 디지털 서명 알고리즘은 현재 사용하는 기존 디지털 서명 알고리즘(RSA-PSS, ECDSA 등)과 동등하며, 여기서 한 당사자는 공개/프라이빗 키 쌍을 생성하고 프라이빗 키로 메시지(또는 해시 값)에 서명하여 서명을 생성합니다. 연결된 공개 키가 있는 모든 사용자가 서명을 확인할 수 있습니다. 한 가지 차이점은 PQ 디지털 서명 알고리즘은 해시 전 변형을 지원하여 기존 서명 알고리즘과 유사한 데이터와 임의의 길이의 입력 데이터에 서명하는 순수 변형에 서명한다는 것입니다. 이 예제에서는 순수 ML-DSA 변형을 사용하고 해시 전 ML-DSA를 사용하는 방법을 설명합니다. 개발자는 이러한 단계를 수행하여 애플리케이션에서 안전한 양자 후 디지털 서명을 구현할 수 있습니다.
알고리즘 핸들 및 키 쌍 설정
다음 단계에 따라 ML-DSA에 대한 알고리즘 핸들 및 키 쌍을 설정합니다.
기본 Microsoft 기본 공급자 또는 다른 HSM 공급자를 선택하여 ML-DSA 알고리즘에 BCryptOpenAlgorithmProvider 를 사용합니다. 이 단계에서는 후속 작업에 대한 암호화 컨텍스트를 설정합니다.
status = BCryptOpenAlgorithmProvider( &hAlg, BCRYPT_MLDSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }BCryptGenerateKeyPair를 사용하여 선택한 알고리즘에 대한 프라이빗 및 퍼블릭 키를 만듭니다.
status = BCryptGenerateKeyPair(hAlg, &hKeyPair, 0, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }매개 변수는 다음과 같습니다.
- hAlg: BCryptOpenAlgorithmProvider에서 가져온 알고리즘 공급자에 대한 핸들입니다.
- hKeyPair: 키 쌍의 핸들입니다.
-
0: ML-DSA의 기본 키 크기를 나타냅니다. -
NULL: 이 작업에 대한 플래그가 설정되지 않았습니다.
BCryptSetProperty를 사용하여 강도 및 성능에 대한 장단점이 있는 ML-DSA에 사용할 매개 변수 집합을 지정합니다. 이 예제에서는 ML-DSA-44 매개 변수 집합이 선택됩니다.
status = BCryptSetProperty(&hKeyPair, BCRYPT_PARAMETER_SET_NAME, (PUCHAR)BCRYPT_MLDSA_PARAMETER_SET_44, sizeof(BCRYPT_MLDSA_PARAMETER_SET_44), 0); if (!NT_SUCCESS(status)) { goto cleanup; }BCRYPT_PARAMETER_SET_NAME 설정은 사용할 매개 변수 집합(예: ML-DSA-44)을 나타냅니다.
공개 및 프라이빗 키를 사용할 준비가 되도록 BCryptFinalizeKeyPair 를 사용합니다.
status = BCryptFinalizeKeyPair(hKeyPair, NULL); if (!NT_SUCCESS(status)) { goto cleanup; } // Public/Private key pair is generated at this point
메시지에 서명
생성된 키 쌍을 사용하여 메시지에 서명하기 위해 코드는 BCryptSignHash를 사용합니다.
코드는 먼저 출력을 사용하여 BCryptSignHash
NULL를 호출하여 서명에 필요한 버퍼 크기를 결정합니다. 그런 다음 서명에 대한 메모리를 할당합니다.// Get the signature size status = BCryptSignHash(hKeyPair, NULL, NULL, 0, NULL, 0, &cbSignature, NULL); if (!NT_SUCCESS(status)) { goto cleanup; } pbSignature = (PBYTE)LocalAlloc(LMEM_FIXED, cbSignature); if (pbSignature == NULL) { status = STATUS_NO_MEMORY; goto cleanup; }다음으로, BCRYPT_PQDSA_PADDING_INFO 구조를 구성합니다.
// Sign with pure ML-DSA // // Pure variants sign arbitrary length messages whereas // the pre-hash variants sign a hash value of the input. // To sign with pure ML-DSA or pure SLH-DSA, pszPrehashAlgId must be set // to NULL if BCRYPT_PQDSA_PADDING_INFO is provided to the sign/verify // functions. // For pre-hash signing, the hash algorithm used in creating // the hash of the input must be specified using the pszPrehashAlgId // field of BCRYPT_PQDSA_PADDING_INFO. This hash algorithm information // is required in generating and verifying the signature as the OID of // the hash algorithm becomes a prefix of the input to be signed. // padinfo.pbCtx = (PUCHAR)ctx; padinfo.cbCtx = sizeof(ctx); padinfo.pszPrehashAlgId = NULL;BCRYPT_PQDSA_PADDING_INFO 구조체에는 다음 필드가 포함됩니다.
- pbCtx 및 cbCtx: 선택적 컨텍스트 문자열(추가 인증된 데이터)에 대한 포인터 및 크기입니다.
-
pszPrehashAlgId: "pure" 서명으로
NULL설정합니다(메시지는 해시가 아니라 직접 서명됨).
마지막으로 실제 서명은 BCryptSignHash에 대한 또 다른 호출을 사용하여 생성됩니다. 키 핸들, 패딩 정보, 메시지 및 할당된 서명 버퍼가 호출에 전달됩니다. BCRYPT_PAD_PQDSA 플래그는 PQDSA 패딩을 지정합니다.
status = BCryptSignHash( hKeyPair, &padinfo, (PUCHAR)msg, sizeof(msg), pbSignature, cbSignature, &cbWritten, BCRYPT_PAD_PQDSA); if (!NT_SUCCESS(status)) { goto cleanup; }
공개 키 내보내기
이 섹션에서는 다른 사용자가 서명을 확인할 수 있도록 공개 키를 내보냅니다.
BCryptExportKey는 먼저 매개 변수 및
NULL형식으로 호출 되어 필요한 버퍼 크기를 가져옵니다.// Get the public key blob size status = BCryptExportKey( hKeyPair, NULL, BCRYPT_PQDSA_PUBLIC_BLOB, NULL, 0, &cbPublicKeyBlob, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }버퍼가 할당되고 BCryptExportKey 가 다시 호출되어 공개 키를 내보냅니다.
pbPublicKeyBlob = (PBYTE)LocalAlloc(LMEM_FIXED, cbPublicKeyBlob); if (pbPublicKeyBlob == NULL) { status = STATUS_NO_MEMORY; goto cleanup; } // Export the public key status = BCryptExportKey( hKeyPair, NULL, BCRYPT_PQDSA_PUBLIC_BLOB, pbPublicKeyBlob, cbPublicKeyBlob, &cbWritten, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }
내보낸 키는 BCRYPT_PQDSA_PUBLIC_BLOB 형식입니다.
자원을 정리하세요
정리 단계에서는 버퍼 및 핸들과 같은 할당된 리소스가 해제되어 메모리 누수 또는 유효하지 않은 핸들이 발생하지 않도록 합니다.
cleanup:
if (pbPublicKeyBlob)
{
LocalFree(pbPublicKeyBlob);
}
if (pbSignature)
{
LocalFree(pbSignature);
}
if (hKeyPair != NULL)
{
BCryptDestroyKey(hKeyPair);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
서명 확인
서명을 확인하기 위해 코드는 BCryptVerifySignature를 사용합니다.
- 아래 샘플 코드의 MLDSAVerifySample 함수는 내보낸 공개 키 Blob, 메시지, 컨텍스트 및 서명을 받습니다.
- BCryptImportKeyPair 는 공개 키 Blob을 가져와 API에서 사용할 수 있는 키 핸들을 만듭니다.
- 다시 BCRYPT_PQDSA_PADDING_INFO 컨텍스트를 사용하여 준비됩니다(서명하는 동안 사용된 것과 일치해야 합니다). 다음 필드가 설정됩니다.
- pbCtx/cbCtx (컨텍스트): 서명을 특정 애플리케이션 또는 사용 사례에 바인딩하는 데 도움이 되는 추가 데이터를 제공하는 데 사용됩니다. 서명 및 확인 모두 동일한 컨텍스트를 사용해야 합니다.
-
pszPrehashAlgId: "pure" ML-DSA이므로
NULL로 설정합니다. 해시 전 변형의 경우 해시 알고리즘 ID를 지정합니다. - BCRYPT_PAD_PQDSA: PQDSA 패딩을 후양자 보안에 사용하도록 지정합니다.
- BCryptVerifySignature 는 키 핸들, 패딩 정보, 원본 메시지 및 서명을 사용하여 호출됩니다. 함수가 성공을 반환하면 서명이 유효합니다. 그렇지 않으면 그렇지 않습니다.
위의 단계는 아래 코드 샘플에 주석으로 설명되어 있습니다.
//
// This function takes as input an ML-DSA-44 public key blob,
// and a signature generated by the same algorithm along with
// the message the signature belongs to. It verifies whether the
// signature is valid or not.
//
// STEP 1: Receive the public key blob, message, context, and signature
NTSTATUS MLDSAVerifySample(
PCBYTE pbPublicKeyBlob,
ULONG cbPublicKeyBlob,
PCBYTE pbMsg,
ULONG cbMsg,
PCBYTE pbContext,
ULONG cbContext,
PCBYTE pbSignature,
ULONG cbSignature)
{
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
BCRYPT_PQDSA_PADDING_INFO padinfo = { 0 };
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_MLDSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// STEP 2: Import the public key
// Import the public key
status = BCryptImportKeyPair(
hAlg,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
&hKey,
(PUCHAR)pbPublicKeyBlob,
cbPublicKeyBlob,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// STEP 3: Prepare the padding info
// Verify the signature
// Assuming the signature is generated with pure ML-DSA.
// Otherwise pszPrehashAlgId must be set to the identifier
// of the hash algorithm used in pre-hashing the input.
padinfo.pbCtx = (PUCHAR)pbContext;
padinfo.cbCtx = cbContext;
padinfo.pszPrehashAlgId = NULL;
// STEP 4: Verify the signature
status = BCryptVerifySignature(
hKey,
&padinfo,
(PUCHAR)pbMsg, // pbHash
cbMsg, // cbHash
(PUCHAR)pbSignature,
cbSignature,
BCRYPT_PAD_PQDSA);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
cleanup:
if (hKey != NULL)
{
BCryptDestroyKey(hKey);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
return status;
}
블록에서 cleanup: 코드는 BCryptDestroyKey 를 사용하여 메모리에서 키 핸들을 삭제하고 BCryptCloseAlgorithmProvider 를 사용하여 메모리에서 알고리즘 핸들을 삭제합니다.
전체 코드 샘플 검토
다음 코드 샘플에서는 Microsoft의 CNG API에서 ML-DSA 알고리즘을 사용하여 디지털 서명을 생성하고 확인하는 전체 프로세스를 보여 줍니다.
//
// Sample ML-DSA code
//
// This function creates an ML-DSA-44 private key, signs
// a message with it, and exports the public-key. The receiver
// can use the public key, message, and the signature to verify
// that they are generated by the same party who owns the private key.
//
#define SAMPLE_MESSAGE "message"
#define SAMPLE_CONTEXT "context"
NTSTATUS MLDSASignSample()
{
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKeyPair = NULL;
BCRYPT_PQDSA_PADDING_INFO padinfo = { 0 };
PBYTE pbSignature = NULL;
ULONG cbSignature, cbWritten;
const BYTE msg[] = SAMPLE_MESSAGE;
const BYTE ctx[] = SAMPLE_CONTEXT;
PBYTE pbPublicKeyBlob = NULL;
ULONG cbPublicKeyBlob;
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_MLDSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = BCryptGenerateKeyPair(hAlg, &hKeyPair, 0, NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = BCryptSetProperty(&hKeyPair, BCRYPT_PARAMETER_SET_NAME, (PUCHAR)BCRYPT_MLDSA_PARAMETER_SET_44, sizeof(BCRYPT_MLDSA_PARAMETER_SET_44), 0);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = BCryptFinalizeKeyPair(hKeyPair, NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// Public/Private key pair is generated at this point
// Get the signature size
status = BCryptSignHash(hKeyPair, NULL, NULL, 0, NULL, 0, &cbSignature, NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
pbSignature = (PBYTE)LocalAlloc(LMEM_FIXED, cbSignature);
if (pbSignature == NULL) {
status = STATUS_NO_MEMORY;
goto cleanup;
}
//
// Sign with pure ML-DSA
//
// Pure variants sign arbitrary length messages whereas
// the pre-hash variants sign a hash value of the input.
// To sign with pure ML-DSA or pure SLH-DSA, pszPrehashAlgId must be set
// to NULL if BCRYPT_PQDSA_PADDING_INFO is provided to the sign/verify
// functions.
// For pre-hash signing, the hash algorithm used in creating
// the hash of the input must be specified using the pszPrehashAlgId
// field of BCRYPT_PQDSA_PADDING_INFO. This hash algorithm information
// is required in generating and verifying the signature as the OID of
// the hash algorithm becomes a prefix of the input to be signed.
//
padinfo.pbCtx = (PUCHAR)ctx;
padinfo.cbCtx = sizeof(ctx);
padinfo.pszPrehashAlgId = NULL;
status = BCryptSignHash(
hKeyPair,
&padinfo,
(PUCHAR)msg,
sizeof(msg),
pbSignature,
cbSignature,
&cbWritten,
BCRYPT_PAD_PQDSA);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
//
// Export the public key
//
// Get the public key blob size
status = BCryptExportKey(
hKeyPair,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
NULL,
0,
&cbPublicKeyBlob,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
pbPublicKeyBlob = (PBYTE)LocalAlloc(LMEM_FIXED, cbPublicKeyBlob);
if (pbPublicKeyBlob == NULL) {
status = STATUS_NO_MEMORY;
goto cleanup;
}
// Export the public key
status = BCryptExportKey(
hKeyPair,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
pbPublicKeyBlob,
cbPublicKeyBlob,
&cbWritten,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
cleanup:
if (pbPublicKeyBlob)
{
LocalFree(pbPublicKeyBlob);
}
if (pbSignature)
{
LocalFree(pbSignature);
}
if (hKeyPair != NULL)
{
BCryptDestroyKey(hKeyPair);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
return status;
}
//
// This function takes as input an ML-DSA-44 public key blob,
// and a signature generated by the same algorithm along with
// the message the signature belongs to. It verifies whether the
// signature is valid or not.
//
NTSTATUS MLDSAVerifySample(
PCBYTE pbPublicKeyBlob,
ULONG cbPublicKeyBlob,
PCBYTE pbMsg,
ULONG cbMsg,
PCBYTE pbContext,
ULONG cbContext,
PCBYTE pbSignature,
ULONG cbSignature)
{
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
BCRYPT_PQDSA_PADDING_INFO padinfo = { 0 };
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_MLDSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// Import the public key
status = BCryptImportKeyPair(
hAlg,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
&hKey,
(PUCHAR)pbPublicKeyBlob,
cbPublicKeyBlob,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// Verify the signature
// Assuming the signature is generated with pure ML-DSA.
// Otherwise pszPrehashAlgId must be set to the identifier
// of the hash algorithm used in pre-hashing the input.
padinfo.pbCtx = (PUCHAR)pbContext;
padinfo.cbCtx = cbContext;
padinfo.pszPrehashAlgId = NULL;
status = BCryptVerifySignature(
hKey,
&padinfo,
(PUCHAR)pbMsg, // pbHash
cbMsg, // cbHash
(PUCHAR)pbSignature,
cbSignature,
BCRYPT_PAD_PQDSA);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
cleanup:
if (hKey != NULL)
{
BCryptDestroyKey(hKey);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
return status;
}