Freigeben über


Verwenden von ML-DSA mit CNG

Hinweis

Einige Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Veröffentlichung erheblich geändert werden kann. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen. Das in diesem Thema beschriebene Feature ist in Vorabversionen der Windows Insider Previewverfügbar.

Dieser Artikel enthält einen Leitfaden zur Implementierung des End-to-End-Workflows zum Erstellen und Überprüfen digitaler Signaturen mithilfe des ML-DSA Algorithmus mit der CNG-API von Microsoft.

Beispielcode ML-DSA mit BCrypt

Erfahren Sie, wie Sie den ML-DSA Algorithmus mit der CNG-API von Microsoft für digitale Signaturen verwenden. Das BCrypt-Beispiel enthält Funktionen zum Signieren einer Nachricht und zum Überprüfen der Signatur sowie zum Exportieren des öffentlichen Schlüssels. Der Code ist so konzipiert, dass er leicht zu verfolgen und zu verstehen ist, sodass er für Entwickler geeignet ist, die post quantum digitale Signaturen in ihren Anwendungen implementieren möchten.

Signaturgenerierung und Überprüfung mit ML-DSA

In diesem Codebeispiel wird der End-to-End-Workflow zum Erstellen und Überprüfen digitaler Signaturen mithilfe des ML-DSA Algorithmus mit der CNG-API von Microsoft veranschaulicht. Es hebt die Bedeutung des Abgleichskontexts während der Signierung und Überprüfung sowie die sorgfältige Verwaltung von kryptografischen Handles und Arbeitsspeicher hervor. Post-Quantum-Algorithmen für digitale Signaturen sind gleichbedeutend mit den vorhandenen Algorithmen für digitale Signaturen, die wir heute verwenden (RSA-PSS, ECDSA usw.), wobei eine Partei ein öffentliches/privates Schlüsselpaar generiert und eine Nachricht (oder ihren Hashwert) mit dem privaten Schlüssel signiert, um eine Signatur zu erzeugen. Die Signatur kann von jeder Person überprüft werden, die über den zugehörigen öffentlichen Schlüssel verfügt. Ein Unterschied besteht darin, dass PQ digitale Signaturalgorithmen Pre-Hash-Varianten unterstützen, die dann Hashdaten ähnlich wie herkömmliche Signaturalgorithmen signieren, sowie reine Varianten, die alle Eingabedaten beliebiger Länge signieren. In diesem Beispiel wird die reine ML-DSA-Variante verwendet, und es wird beschrieben, wie das Prähashing bei ML-DSA genutzt wird. Mithilfe dieser Schritte können Entwickler sichere, post quantum digitale Signaturen in ihren Anwendungen implementieren.

Einrichten von Algorithmushandles und Schlüsselpaaren

Folgen Sie diesen Schritten, um die Algorithmushandles und Schlüsselpaare für ML-DSA einzurichten:

  1. Verwenden Sie BCryptOpenAlgorithmProvider für den ML-DSA Algorithmus, und wählen Sie entweder den Standardanbieter des Microsoft-Grundtyps oder einen anderen HSM-Anbieter aus. In diesem Schritt wird der kryptografische Kontext für nachfolgende Vorgänge eingerichtet.

    status = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_MLDSA_ALGORITHM,
        MS_PRIMITIVE_PROVIDER,
        NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    
  2. Verwenden Sie BCryptGenerateKeyPair , um private und öffentliche Schlüssel für den ausgewählten Algorithmus zu erstellen.

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

    Die Parameter sind wie folgt:

    • hAlg: Das Handle für den Algorithmusanbieter, der von BCryptOpenAlgorithmProvider abgerufen wurde.
    • hKeyPair: Das Handle für das Schlüsselpaar.
    • 0: Gibt die Standardschlüsselgröße für ML-DSA an.
    • NULL: Für diesen Vorgang sind keine Kennzeichnungen festgelegt.
  3. Verwenden Sie BCryptSetProperty, um das Parameterset anzugeben, das für ML-DSA verwendet werden soll, bei dem zwischen Stärke und Leistung abgewogen wird. In diesem Beispiel wird der Parametersatz ML-DSA-44 ausgewählt.

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

    Das Festlegen von BCRYPT_PARAMETER_SET_NAME gibt an, welches Parameterset verwendet werden soll (z. B. ML-DSA-44).

  4. Verwenden Sie BCryptFinalizeKeyPair , damit der öffentliche und private Schlüssel einsatzbereit sind.

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

Signieren der Nachricht

Um eine Nachricht mit dem generierten Schlüsselpaar zu signieren, verwendet der Code BCryptSignHash.

  1. Der Code bestimmt zuerst die Puffergröße, die für die Signatur benötigt wird, indem BCryptSignHash mit NULL Ausgabe aufgerufen wird. Anschließend wird Speicher für die Signatur zugewiesen.

     // 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. Als Nächstes konfiguriert sie eine BCRYPT_PQDSA_PADDING_INFO Struktur:

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

    Die BCRYPT_PQDSA_PADDING_INFO Struktur enthält die folgenden Felder:

    • pbCtx und cbCtx: Ein Zeiger und eine Größe für eine optionale Kontextzeichenfolge (zusätzliche authentifizierte Daten).
    • pszPrehashAlgId: Auf "reine" Signatur festlegen NULL (die Nachricht ist direkt signiert, kein Hash).
  3. Schließlich wird die eigentliche Signatur mit einem anderen Aufruf von BCryptSignHash generiert. Das Schlüsselhandle, die Padding-Informationen, die Nachricht und der zugeordnete Signaturpuffer werden an den Aufruf übergeben. Das BCRYPT_PAD_PQDSA-Flag gibt den PQDSA-Abstand an.

    status = BCryptSignHash(
        hKeyPair,
        &padinfo,
        (PUCHAR)msg,
        sizeof(msg),
        pbSignature,
        cbSignature,
        &cbWritten,
        BCRYPT_PAD_PQDSA); 
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    } 
    

Exportieren des öffentlichen Schlüssels

In diesem Abschnitt wird der öffentliche Schlüssel exportiert, damit andere die Signaturen überprüfen können.

  1. BCryptExportKey wird zuerst mit NULL Parametern und dem BCRYPT_PQDSA_PUBLIC_BLOB Format aufgerufen, um die erforderliche Puffergröße abzurufen:

    // 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. Der Puffer ist überlastet, und BCryptExportKey wird erneut aufgerufen, um den öffentlichen Schlüssel zu exportieren:

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

Der exportierte Schlüssel befindet sich im BCRYPT_PQDSA_PUBLIC_BLOB Format.

Bereinigen von Ressourcen

Im Bereinigungsschritt werden zugeordnete Ressourcen wie Puffer und Handles freigegeben, was sicherstellt, dass keine Speicherverluste oder hängende Handles vorhanden sind.

cleanup: 

if (pbPublicKeyBlob)
{
    LocalFree(pbPublicKeyBlob);
}

if (pbSignature)
{
    LocalFree(pbSignature);
} 

if (hKeyPair != NULL)
{
    BCryptDestroyKey(hKeyPair);
}

if (hAlg != NULL)
{
    BCryptCloseAlgorithmProvider(hAlg, NULL);
}

Überprüfen der Signatur

Um eine Signatur zu überprüfen, verwendet der Code BCryptVerifySignature.

  1. Die Funktion MLDSAVerifySample im folgenden Beispielcode empfängt den exportierten öffentlichen Schlüssel-BLOB, die Nachricht, den Kontext und die Signatur.
  2. BCryptImportKeyPair importiert das öffentliche Schlüssel-BLOB, um ein schlüsselhandle zu erstellen, das von der API verwendet werden kann.
  3. Erneut wird BCRYPT_PQDSA_PADDING_INFO mit dem Kontext vorbereitet (es sollte mit dem Kontext übereinstimmen, der bei der Unterzeichnung verwendet wird). Die folgenden Felder werden festgelegt:
    • PbCtx/cbCtx (Kontext): Wird verwendet, um zusätzliche Daten zu liefern, die helfen können, Signaturen an eine bestimmte Anwendung oder einen bestimmten Anwendungsfall zu binden. Sowohl das Signieren als auch die Überprüfung müssen denselben Kontext verwenden.
    • pszPrehashAlgId: Setzen Sie auf NULL für "pure" ML-DSA. Bei Vorabhashvarianten gibt sie die Hashalgorithmus-ID an.
    • BCRYPT_PAD_PQDSA: Gibt die Verwendung des PQDSA-Padding für die Post-Quantum-Sicherheit an.
  4. BCryptVerifySignature wird mit dem Schlüsselgriff, den Padding-Informationen, der ursprünglichen Nachricht und der Signatur aufgerufen. Wenn die Funktion Erfolg zurückgibt, ist die Signatur gültig; andernfalls ist es nicht.

Die obigen Schritte werden im folgenden Codebeispiel kommentiert:

//
// 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: Im Block verwendet der Code BCryptDestroyKey, um das Schlüsselhandle aus dem Speicher zu löschen, und BCryptCloseAlgorithmProvider, um das Algorithmushandle aus dem Speicher zu löschen.

Überprüfen des vollständigen Codebeispiels

Im folgenden Codebeispiel wird der vollständige Prozess zum Generieren und Überprüfen einer digitalen Signatur mithilfe des ML-DSA Algorithmus mit der CNG-API von Microsoft veranschaulicht:

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