다음을 통해 공유


CNG에서 ML-DSA 사용

비고

일부 정보는 상용 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. 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에 대한 알고리즘 핸들 및 키 쌍을 설정합니다.

  1. 기본 Microsoft 기본 공급자 또는 다른 HSM 공급자를 선택하여 ML-DSA 알고리즘에 BCryptOpenAlgorithmProvider 를 사용합니다. 이 단계에서는 후속 작업에 대한 암호화 컨텍스트를 설정합니다.

    status = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_MLDSA_ALGORITHM,
        MS_PRIMITIVE_PROVIDER,
        NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    
  2. BCryptGenerateKeyPair를 사용하여 선택한 알고리즘에 대한 프라이빗 및 퍼블릭 키를 만듭니다.

    status = BCryptGenerateKeyPair(hAlg, &hKeyPair, 0, NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    

    매개 변수는 다음과 같습니다.

    • hAlg: BCryptOpenAlgorithmProvider에서 가져온 알고리즘 공급자에 대한 핸들입니다.
    • hKeyPair: 키 쌍의 핸들입니다.
    • 0: ML-DSA의 기본 키 크기를 나타냅니다.
    • NULL: 이 작업에 대한 플래그가 설정되지 않았습니다.
  3. 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)을 나타냅니다.

  4. 공개 및 프라이빗 키를 사용할 준비가 되도록 BCryptFinalizeKeyPair 를 사용합니다.

    status = BCryptFinalizeKeyPair(hKeyPair, NULL); 
    if (!NT_SUCCESS(status)) { 
        goto cleanup; 
    }
    
    // Public/Private key pair is generated at this point 
    

메시지에 서명

생성된 키 쌍을 사용하여 메시지에 서명하기 위해 코드는 BCryptSignHash를 사용합니다.

  1. 코드는 먼저 출력을 사용하여 BCryptSignHashNULL 를 호출하여 서명에 필요한 버퍼 크기를 결정합니다. 그런 다음 서명에 대한 메모리를 할당합니다.

     // 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;
    }
    
  2. 다음으로, 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 구조체에는 다음 필드가 포함됩니다.

    • pbCtxcbCtx: 선택적 컨텍스트 문자열(추가 인증된 데이터)에 대한 포인터 및 크기입니다.
    • pszPrehashAlgId: "pure" 서명으로 NULL 설정합니다(메시지는 해시가 아니라 직접 서명됨).
  3. 마지막으로 실제 서명은 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;
    } 
    

공개 키 내보내기

이 섹션에서는 다른 사용자가 서명을 확인할 수 있도록 공개 키를 내보냅니다.

  1. 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;
    }
    
  2. 버퍼가 할당되고 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를 사용합니다.

  1. 아래 샘플 코드의 MLDSAVerifySample 함수는 내보낸 공개 키 Blob, 메시지, 컨텍스트 및 서명을 받습니다.
  2. BCryptImportKeyPair 는 공개 키 Blob을 가져와 API에서 사용할 수 있는 키 핸들을 만듭니다.
  3. 다시 BCRYPT_PQDSA_PADDING_INFO 컨텍스트를 사용하여 준비됩니다(서명하는 동안 사용된 것과 일치해야 합니다). 다음 필드가 설정됩니다.
    • pbCtx/cbCtx (컨텍스트): 서명을 특정 애플리케이션 또는 사용 사례에 바인딩하는 데 도움이 되는 추가 데이터를 제공하는 데 사용됩니다. 서명 및 확인 모두 동일한 컨텍스트를 사용해야 합니다.
    • pszPrehashAlgId: "pure" ML-DSA이므로 NULL로 설정합니다. 해시 전 변형의 경우 해시 알고리즘 ID를 지정합니다.
    • BCRYPT_PAD_PQDSA: PQDSA 패딩을 후양자 보안에 사용하도록 지정합니다.
  4. 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;
}