ML-DSA gebruiken met CNG

Opmerking

Sommige informatie heeft betrekking op een prereleaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt vrijgegeven. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt. De functie die in dit onderwerp wordt beschreven, is beschikbaar in voorlopige versies van de Windows Insider Preview-.

Dit artikel bevat een handleiding voor het implementeren van de end-to-end-werkstroom voor het maken en verifiëren van digitale handtekeningen met behulp van het ML-DSA-algoritme met de CNG-API van Microsoft.

Voorbeeldcode ML-DSA met BCrypt

Meer informatie over het gebruik van het ML-DSA-algoritme met de CNG-API van Microsoft voor digitale handtekeningen. Het BCrypt-voorbeeld bevat functies voor het ondertekenen van een bericht en het verifiëren van de handtekening, evenals het exporteren van de openbare sleutel. De code is ontworpen om gemakkelijk te volgen en te begrijpen, waardoor deze geschikt is voor ontwikkelaars die digitale handtekeningen na kwantum willen implementeren in hun toepassingen.

Handtekening genereren en verifiëren met ML-DSA

In dit codevoorbeeld ziet u de end-to-end werkstroom voor het maken en verifiëren van digitale handtekeningen met behulp van het ML-DSA-algoritme met de CNG-API van Microsoft. Het markeert het belang van overeenkomende context tijdens ondertekening en verificatie, evenals zorgvuldig beheer van cryptografische ingangen en geheugen. Post-kwantumalgoritmen voor digitale handtekeningen zijn op hoog niveau gelijk aan de bestaande digitale handtekeningalgoritmen die we vandaag gebruiken (RSA-PSS, ECDSA, enzovoort), waarbij één partij een openbaar/persoonlijk sleutelpaar genereert en een bericht (of de hashwaarde) ondertekent met de persoonlijke sleutel om een handtekening te produceren. De handtekening kan worden geverifieerd door iedereen met de bijbehorende openbare sleutel. Een verschil is dat PQ digitale handtekeningalgoritmen ondersteuning bieden voor pre-hashvarianten, die hash vervolgens gegevens ondertekenen die vergelijkbaar zijn met traditionele handtekeningalgoritmen, evenals pure varianten, die invoergegevens van willekeurige lengte ondertekenen. In dit voorbeeld wordt gebruikgemaakt van de pure ML-DSA variant en wordt beschreven hoe u pre-hash ML-DSA gebruikt. Door deze stappen te volgen, kunnen ontwikkelaars veilige digitale handtekeningen na kwantum implementeren in hun toepassingen.

Algoritmegrepen en sleutelparen instellen

U volgt deze stappen om de algoritme-handles en sleutelparen voor ML-DSA in te stellen:

  1. Gebruik BCryptOpenAlgorithmProvider voor het ML-DSA algoritme, waarbij u de standaard microsoft primitieve provider of een andere HSM-provider kiest. Met deze stap stelt u de cryptografische context in voor volgende bewerkingen.

    status = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_MLDSA_ALGORITHM,
        MS_PRIMITIVE_PROVIDER,
        NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    
  2. Gebruik BCryptGenerateKeyPair om persoonlijke en openbare sleutels te maken voor het gekozen algoritme.

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

    De parameters zijn als volgt:

    • hAlg: de ingang voor de algoritmeprovider, die is verkregen van BCryptOpenAlgorithmProvider.
    • hKeyPair: het handvat voor het sleutelpaar.
    • 0: Geeft de standaardsleutelgrootte voor ML-DSA aan.
    • NULL: Er zijn geen vlaggen ingesteld voor deze bewerking.
  3. Gebruik BCryptSetProperty om de parameter op te geven die moet worden gebruikt voor ML-DSA, met compromissen voor sterkte en prestaties. In dit voorbeeld wordt de parameterset ML-DSA-44 gekozen.

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

    Instelling BCRYPT_PARAMETER_SET_NAME geeft aan welke parameterset moet worden gebruikt (bijvoorbeeld ML-DSA-44).

  4. Gebruik BCryptFinalizeKeyPair zodat de openbare en persoonlijke sleutel gereed zijn om te worden gebruikt.

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

Het bericht ondertekenen

Als u een bericht wilt ondertekenen met het gegenereerde sleutelpaar, gebruikt de code BCryptSignHash.

  1. De code bepaalt eerst de buffergrootte die nodig is voor de handtekening door BCryptSignHash aan te roepen met NULL uitvoer. Vervolgens wordt geheugen toegewezen voor de handtekening.

     // 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. Vervolgens wordt een BCRYPT_PQDSA_PADDING_INFO structuur geconfigureerd:

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

    De BCRYPT_PQDSA_PADDING_INFO structuur bevat de volgende velden:

    • pbCtx en cbCtx: een aanwijzer en grootte voor een optionele contexttekenreeks (aanvullende geverifieerde gegevens).
    • pszPrehashAlgId: ingesteld op NULL 'pure' ondertekening (het bericht wordt rechtstreeks ondertekend, geen hash).
  3. Ten slotte wordt de werkelijke handtekening gegenereerd met een andere aanroep naar BCryptSignHash. De sleutelgreep, opvulinformatie, bericht en toegewezen handtekeningbuffer worden doorgegeven aan de aanroep. De BCRYPT_PAD_PQDSA vlag specificeert PQDSA-padding.

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

De openbare sleutel exporteren

In deze sectie wordt de openbare sleutel geëxporteerd, zodat anderen de handtekeningen kunnen verifiëren.

  1. BCryptExportKey wordt eerst aangeroepen met NULL parameters en de BCRYPT_PQDSA_PUBLIC_BLOB-indeling om de vereiste buffergrootte op te halen:

    // 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. De buffer wordt gealloceerd en BCryptExportKey wordt opnieuw aangeroepen om de openbare sleutel te exporteren.

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

De geëxporteerde sleutel is in het BCRYPT_PQDSA_PUBLIC_BLOB-formaat.

De hulpbronnen opschonen

In de opschoonstap worden toegewezen resources, zoals buffers en handles, vrijgegeven, om te zorgen dat er geen geheugenlekken of zwevende handles zijn.

cleanup: 

if (pbPublicKeyBlob)
{
    LocalFree(pbPublicKeyBlob);
}

if (pbSignature)
{
    LocalFree(pbSignature);
} 

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

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

De handtekening controleren

Om een handtekening te verifiëren, gebruikt de code BCryptVerifySignature.

  1. De functie MLDSAVerifySample in de onderstaande voorbeeldcode ontvangt de geëxporteerde blob met openbare sleutels, het bericht, de context en de handtekening.
  2. BCryptImportKeyPair importeert de blob van de openbare sleutel om een sleutelgreep te maken die kan worden gebruikt door de API.
  3. Opnieuw wordt BCRYPT_PQDSA_PADDING_INFO voorbereid met de context (deze moet overeenkomen met de context die tijdens het ondertekenen wordt gebruikt). De volgende velden zijn ingesteld:
    • pbCtx/cbCtx (context): wordt gebruikt voor het leveren van aanvullende gegevens, waarmee handtekeningen aan een specifieke toepassing of use-case kunnen worden gekoppeld. Zowel ondertekenen als verifiëren moeten dezelfde context gebruiken.
    • pszPrehashAlgId: ingesteld op NULL voor "pure" ML-DSA. Voor pre-hashvarianten wordt de id van het hash-algoritme opgegeven.
    • BCRYPT_PAD_PQDSA: Geeft het gebruik van PQDSA-opvulling voor post-kwantumbeveiliging aan.
  4. BCryptVerifySignature wordt aangeroepen met de sleutelgreep, opvullingsgegevens, het oorspronkelijke bericht en de handtekening. Als de functie een succes retourneert, is de handtekening geldig; anders is het niet.

De bovenstaande stappen worden geannoteerd in het onderstaande codevoorbeeld:

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

In het cleanup: blok gebruikt de code BCryptDestroyKey om de sleutelgreep uit het geheugen te verwijderen en BCryptCloseAlgorithmProvider om de algoritmegreep uit het geheugen te verwijderen.

Bekijk het volledige codevoorbeeld

In het volgende codevoorbeeld ziet u het volledige proces voor het genereren en verifiëren van een digitale handtekening met behulp van het ML-DSA-algoritme met de CNG-API van Microsoft:

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