Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
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:
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; }
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.
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).
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.
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; }
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).
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.
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; }
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.
- 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.
- BCryptImportKeyPair importa o blob de chave pública para criar um identificador de chave utilizável pela API.
- 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.
- 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;
}