次の方法で共有


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 など) と高いレベルで同等です。一方のパーティが公開キーと秘密キーのペアを生成し、秘密キーを使用してメッセージ (またはそのハッシュ値) に署名し、署名を生成します。 署名は、関連付けられている公開キーを持つすべてのユーザーが確認できます。 1 つの違いは、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. このコードではまず、出力で 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;
    }
    
  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: "純粋" 署名用に 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 パラメーターと BCRYPT_PQDSA_PUBLIC_BLOB 形式で呼び出され、必要なバッファー サイズを取得します。

    // 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;
}