共用方式為


將 ML-DSA 與 CNG 一起使用

備註

某些資訊與一款尚未正式發行的產品有關,該產品在正式推出之前可能會進行大幅修改。 Microsoft 對於此處提供的資訊,不做任何明確或隱含的保證。 本主題所述的功能可在 Windows Insider Preview發行前版本取得。

本文提供實作端對端工作流程的指南,以使用 ML-DSA 演算法搭配 Microsoft 的 CNG API 來建立和驗證數位簽名。

使用 BCrypt 的範例 ML-DSA 程式代碼

瞭解如何使用 ML-DSA 演算法搭配 Microsoft 的 CNG API 進行數位簽名。 BCrypt 範例包含用來簽署訊息和驗證簽章,以及匯出公鑰的函式。 此程式代碼的設計目的是要易於遵循和瞭解,因此適合想要在其應用程式中實作量子後數位簽名的開發人員。

使用 ML-DSA 產生簽章和驗證

此程式代碼範例示範端對端工作流程,以使用具有Microsoft CNG API 的 ML-DSA 演演算法來建立和驗證數字簽名。 它強調在簽署和驗證期間比對內容的重要性,以及仔細管理密碼編譯句柄和記憶體。 量子後數位簽名演算法相當於我們今天使用的現有數位簽名演算法(RSA-PSS、ECDSA 等),其中一方會產生公開/私鑰組,並使用私鑰簽署訊息(或其哈希值),以產生簽章。 簽章可由具有相關聯公鑰的任何人驗證。 其中一個差異是 PQ 數位簽名演算法支援預先哈希變體,其哈希接著會簽署類似於傳統簽章演算法的數據,以及純變異,以簽署任意長度的任何輸入數據。 此範例會使用純 ML-DSA 變體,並說明如何使用預先哈希 ML-DSA。 藉由遵循這些步驟,開發人員可以在其應用程式中實作安全且後置的數字簽名。

設定演算法句柄和金鑰組

您將遵循下列步驟來設定 ML-DSA 的演算法句柄和金鑰組:

  1. 針對 ML-DSA 演算法使用 BCryptOpenAlgorithmProvider ,選擇預設Microsoft基本提供者或其他 HSM 提供者。 此步驟會設定後續作業的密碼編譯內容。

    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:設定為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 (Context):用來提供其他數據,這有助於將簽章系結至特定應用程式或使用案例。 簽署和驗證都必須使用相同的內容。
    • pszPrehashAlgId:設定為NULL「純」ML-DSA。 針對哈希前變體,它會指定哈希演算法標識碼。
    • 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;
}