Partilhar via


Usando ML-DSA com GNC

Observação

Algumas informações referem-se a um produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado comercialmente. A Microsoft não oferece garantias, expressas ou implícitas, em relação às informações fornecidas aqui. O recurso descrito neste tópico está disponível em versões de pré-lançamento do Windows Insider Preview.

Este artigo fornece um guia para implementar o fluxo de trabalho de ponta a ponta para criar e verificar assinaturas digitais usando o algoritmo ML-DSA com a API CNG da Microsoft.

Exemplo de código ML-DSA usando BCrypt

Saiba como usar o algoritmo ML-DSA com a API CNG da Microsoft para assinaturas digitais. O exemplo BCrypt inclui funções para assinar uma mensagem e verificar a assinatura, bem como exportar a chave pública. O código é projetado para ser fácil de seguir e entender, tornando-o adequado para desenvolvedores que procuram implementar assinaturas digitais pós-quânticas em seus aplicativos.

Geração e verificação de assinaturas com ML-DSA

Este exemplo de código demonstra o fluxo de trabalho de ponta a ponta para criar e verificar assinaturas digitais usando o algoritmo ML-DSA com a API CNG da Microsoft. Destaca a importância de alinhar o contexto durante a assinatura digital e verificação, bem como o gerenciamento cuidadoso de identificadores criptográficos e memória. Os algoritmos de assinatura digital pós-quântica são equivalentes em alto nível aos algoritmos de assinatura digital existentes que usamos hoje (RSA-PSS, ECDSA, etc.), onde uma parte gera um par de chaves público/privado e assina uma mensagem (ou seu valor de hash) com a chave privada para produzir uma assinatura. A assinatura pode ser verificada por qualquer pessoa que tenha a chave pública associada. Uma diferença é que os algoritmos de assinatura digital PQ suportam variantes pré-hash, que primeiro fazem hash e depois assinam os dados, de forma semelhante aos algoritmos de assinatura tradicionais, bem como variantes puras, que assinam quaisquer dados de entrada com comprimento arbitrário. Este exemplo utiliza a variante pura ML-DSA e explica como usar o ML-DSA antes de fazer hash. Seguindo essas etapas, os desenvolvedores podem implementar assinaturas digitais pós-quânticas seguras em seus aplicativos.

Configurar identificadores de algoritmo e pares de chaves

Você seguirá estas etapas para configurar os manipuladores de algoritmo e os pares de chaves para ML-DSA:

  1. Use BCryptOpenAlgorithmProvider para o algoritmo ML-DSA, escolhendo o provedor primitivo padrão da Microsoft ou outro provedor HSM. Esta etapa configura o contexto criptográfico para operações subsequentes.

    status = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_MLDSA_ALGORITHM,
        MS_PRIMITIVE_PROVIDER,
        NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    
  2. Use BCryptGenerateKeyPair para criar chaves privadas e públicas para o algoritmo escolhido.

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

    Os parâmetros são os seguintes:

    • hAlg: O identificador para o provedor de algoritmo, que foi obtido de BCryptOpenAlgorithmProvider.
    • hKeyPair: O identificador para o par de chaves.
    • 0: Indica o tamanho de chave padrão para ML-DSA.
    • NULL: Nenhum sinalizador é definido para esta operação.
  3. Use BCryptSetProperty para especificar o conjunto de parâmetros a ser usado para ML-DSA, que tem compensações para força e desempenho. Neste exemplo, o conjunto de parâmetros ML-DSA-44 é escolhido.

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

    A configuração BCRYPT_PARAMETER_SET_NAME indica qual conjunto de parâmetros usar (por exemplo, ML-DSA-44).

  4. Use BCryptFinalizeKeyPair para que as chaves pública e privada estejam prontas para serem usadas.

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

Assine a mensagem

Para assinar uma mensagem com o par de chaves gerado, o código usa BCryptSignHash.

  1. O código primeiro determina o tamanho do buffer necessário para a assinatura chamando BCryptSignHash com a saída NULL. Em seguida, ele aloca memória para a assinatura.

     // 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. Em seguida, ele configura uma estrutura 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;
    

    A estrutura BCRYPT_PQDSA_PADDING_INFO contém os seguintes campos:

    • pbCtx e cbCtx: um ponteiro e tamanho para uma cadeia de caracteres de contexto opcional (dados autenticados adicionais).
    • pszPrehashAlgId: definido como NULL para assinatura "pura" (a mensagem é assinada diretamente, não um hash).
  3. Finalmente, a assinatura real é gerada com outra chamada para BCryptSignHash. O identificador de chave, as informações de padding, a mensagem e o buffer de assinatura digital alocado são passados para a chamada. O indicador BCRYPT_PAD_PQDSA especifica o padding PQDSA.

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

Exportar a chave pública

Nesta seção, a chave pública é exportada para que outras pessoas possam verificar as assinaturas.

  1. BCryptExportKey é chamado primeiro com NULL parâmetros e o formato BCRYPT_PQDSA_PUBLIC_BLOB para obter o tamanho de buffer necessário:

    // 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. O buffer é alocado e BCryptExportKey é chamado novamente para exportar a chave pública:

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

A chave exportada está no formato BCRYPT_PQDSA_PUBLIC_BLOB .

Limpeza de recursos

Na etapa de limpeza, os recursos alocados, como buffers e alças, são liberados, garantindo que não haja vazamentos de memória ou alças penduradas.

cleanup: 

if (pbPublicKeyBlob)
{
    LocalFree(pbPublicKeyBlob);
}

if (pbSignature)
{
    LocalFree(pbSignature);
} 

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

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

Verificar a assinatura

Para verificar uma assinatura, o código usa BCryptVerifySignature.

  1. A função MLDSAVerifySample no código de exemplo abaixo recebe o blob de chave pública exportado, a mensagem, o contexto e a assinatura.
  2. BCryptImportKeyPair importa o blob de chave pública para criar um identificador de chave utilizável pela API.
  3. Novamente, BCRYPT_PQDSA_PADDING_INFO é preparado com o contexto (deve corresponder ao usado durante a assinatura). Os seguintes campos são definidos:
    • pbCtx/cbCtx (Contexto): Usado para fornecer dados adicionais, que podem ajudar a vincular assinaturas a um aplicativo ou caso de uso específico. Tanto a assinatura quanto a verificação devem usar o mesmo contexto.
    • pszPrehashAlgId: definido como NULL ML-DSA "puro". Para variantes pré-hash, ele especifica o ID do algoritmo de hash.
    • BCRYPT_PAD_PQDSA: Especifica o uso de padding PQDSA para segurança pós-quântica.
  4. BCryptVerifySignature é chamado com o identificador de chave, informações de preenchimento, a mensagem original e a assinatura. Se a função retornar sucesso, a assinatura será válida; caso contrário, não é.

As etapas acima são anotadas no exemplo de código abaixo:

//
// 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: No bloco , o código usa BCryptDestroyKey para excluir o identificador de chave da memória e BCryptCloseAlgorithmProvider para excluir o identificador de algoritmo da memória.

Revise o exemplo de código completo

O exemplo de código a seguir demonstra o processo completo de geração e verificação de uma assinatura digital usando o algoritmo ML-DSA com a API CNG da 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;
}