備註
某些資訊與一款尚未正式發行的產品有關,該產品在正式推出之前可能會進行大幅修改。 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 的演算法句柄和金鑰組:
針對 ML-DSA 演算法使用 BCryptOpenAlgorithmProvider ,選擇預設Microsoft基本提供者或其他 HSM 提供者。 此步驟會設定後續作業的密碼編譯內容。
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:設定為
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參數和 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; }緩衝區已配置,並再次呼叫 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 (Context):用來提供其他數據,這有助於將簽章系結至特定應用程式或使用案例。 簽署和驗證都必須使用相同的內容。
-
pszPrehashAlgId:設定為
NULL「純」ML-DSA。 針對哈希前變體,它會指定哈希演算法標識碼。 - 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;
}