Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você 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 realizar uma troca de chaves usando o algoritmo Mecanismo de Encapsulamento de Chaves do Módulo-Lattice-Based (ML-KEM) com a API CNG da Microsoft.
Exemplo de código ML-KEM de encapsulamento e descapsulação de chave usando BCrypt
ML-KEM é um algoritmo pós-quântico usado para troca de chaves e é padronizado no FIPS 203. A troca de chaves é uma parte importante dos protocolos de segurança, como o TLS, em que um cliente e um servidor negociam uma conexão e criam e compartilham material de chave para criptografar e descriptografar mensagens enviadas pela Internet. No KEM, as chaves produzidas no processo de geração de par de chaves são chamadas chave de encapsulamento e chave de descapsulação. A Chave de Encapsulamento é pública e pode ser usada por qualquer pessoa para executar uma operação de Encapsulamento de Chave que produz uma chave secreta (para a parte que executa esta operação) e um texto criptografado. O texto criptografado é fornecido como uma entrada para a operação de descapsulação de chave pelo proprietário da chave privada para recuperar a mesma chave secreta compartilhada que a parte encapsulante obteve durante o processo de Encapsulamento de Chave. Este exemplo demonstra como um aplicativo de servidor e cliente TLS hipotético consumiria as novas APIs de ML-KEM no BCrypt para executar uma troca de chaves.
Configuração e geração de pares de chaves
As etapas a seguir descrevem o processo de configuração da geração e do encapsulamento do par de chaves ML-KEM:
Use BCryptGenerateKeyPair com BCRYPT_MLKEM_ALG_HANDLE para criar um novo par de chaves para encapsulamento de chave. Os campos de comprimento e sinalizadores são ambos
0
, uma vez que os comprimentos de chave são definidos pelo conjunto de parâmetros ML-KEM.// Generate the key pair for key exchange unique_bcrypt_key hKeyPair; THROW_IF_NTSTATUS_FAILED( BCryptGenerateKeyPair( BCRYPT_MLKEM_ALG_HANDLE, &hKeyPair, 0, // dwLength 0)); // dwFlags
Chame BCryptSetProperty no par de chaves para definir o BCRYPT_PARAMETER_SET_NAME como BCRYPT_MLKEM_PARAMETER_SET_768, especificando o conjunto de parâmetros para a operação de ML-KEM. ML-KEM também dá suporte aos conjuntos de parâmetros 512 e 1024 definidos pelo NIST.
THROW_IF_NTSTATUS_FAILED( BCryptSetProperty( &hKeyPair, BCRYPT_PARAMETER_SET_NAME, (PUCHAR) BCRYPT_MLKEM_PARAMETER_SET_768, sizeof(BCRYPT_MLKEM_PARAMETER_SET_768), 0)); // dwFlags
Chame BCryptFinalizeKeyPair para preparar o par de chaves para uso em operações subsequentes.
THROW_IF_NTSTATUS_FAILED( BCryptFinalizeKeyPair( hKeyPair.get(), 0)); // dwFlags
Exportação e Intercâmbio de Chaves Públicas
Chame BCryptExportKey com um
NULL
buffer de saída para verificar o tamanho necessário para exportar o BCRYPT_MLKEM_ENCAPSULATION_BLOB.ULONG cbEncapsulationKeyBlob = 0; THROW_IF_NTSTATUS_FAILED( BCryptExportKey( hKeyPair.get(), NULL, BCRYPT_MLKEM_ENCAPSULATION_BLOB, NULL, // pbOutput 0, // cbOutput &cbEncapsulationKeyBlob, 0)); // dwFlags
Aloque um buffer com base no tamanho recuperado anteriormente e exporte a chave de encapsulamento (pública) usando BCryptExportKey. Esse blob será enviado para o parceiro de troca de chaves (por exemplo, o servidor em um cenário cliente-servidor).
vector<BYTE> encapsulationKeyBlob(cbEncapsulationKeyBlob); THROW_IF_NTSTATUS_FAILED( BCryptExportKey( hKeyPair.get(), NULL, BCRYPT_MLKEM_ENCAPSULATION_BLOB, encapsulationKeyBlob.data(), static_cast<ULONG>(encapsulationKeyBlob.size()), &cbEncapsulationKeyBlob, 0));
Verifique se o BCRYPT_MLKEM_KEY_BLOB tem a mágica pública correta e o conjunto de parâmetros 768.
BCRYPT_MLKEM_KEY_BLOB* pEncapsulationKeyBlob = reinterpret_cast<BCRYPT_MLKEM_KEY_BLOB *>(encapsulationKeyBlob.data()); ASSERT(pEncapsulationKeyBlob->dwMagic == BCRYPT_MLKEM_PUBLIC_MAGIC); ASSERT(pEncapsulationKeyBlob->cbParameterSet == sizeof(BCRYPT_MLKEM_PARAMETER_SET_768)); if (wcscmp(BCRYPT_MLKEM_PARAMETER_SET_768, reinterpret_cast<WCHAR *>(pEncapsulationKeyBlob + 1)) != 0) { return; }
Envie a chave de encapsulamento para o servidor na mensagem de troca de chaves do cliente.
PBYTE pbEncapsulationKey = reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768); ULONG cbEncapsulationKey = pEncapsulationKeyBlob->cbKey; SendToServer(pbEncapsulationKey, cbEncapsulationKey);
Encapsulamento e descapsulação
As etapas a seguir descrevem o processo de encapsular e descapsular a chave secreta compartilhada:
O servidor recebe a mensagem de troca de chaves do cliente e recupera os bytes da chave de encapsulamento.
// Server receives the client's key_exchange message and retrieves the // encapsulation key bytes. vector<BYTE> encapsulationKey = GetClientKeyExchange(); ULONG cbEncapsulationKey = static_cast<ULONG>(encapsulationKey.size());
O servidor coloca a chave em um BCRYPT_KEY_BLOB usando o conjunto de parâmetros 768 e a mágica pública e importa a chave de encapsulamento.
// Put the Key in a BCRYPT_KEY_BLOB and import it. ULONG cbEncapsulationKeyBlob = sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768) + cbEncapsulationKey; vector<BYTE> encapsulationKeyBlob(cbEncapsulationKeyBlob); BCRYPT_MLKEM_KEY_BLOB* pEncapsulationKeyBlob = reinterpret_cast<BCRYPT_MLKEM_KEY_BLOB *>(encapsulationKeyBlob.data()); pEncapsulationKeyBlob->dwMagic = BCRYPT_MLKEM_PUBLIC_MAGIC; pEncapsulationKeyBlob->cbParameterSet = sizeof(BCRYPT_MLKEM_PARAMETER_SET_768); pEncapsulationKeyBlob->cbKey = cbEncapsulationKey; CopyMemory( reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB), BCRYPT_MLKEM_PARAMETER_SET_768, sizeof(BCRYPT_MLKEM_PARAMETER_SET_768)); CopyMemory( reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768), encapsulationKey.data(), encapsulationKey.size()); unique_bcrypt_key hKeyPair; // The server knows the ML-KEM parameter set from the client's // key_exchange, which denotes the parameter set associated with the // encapsulation key it sent. In this case, we know it's // BCRYPT_MLKEM_PARAMETER_SET_768. THROW_IF_NTSTATUS_FAILED( BCryptImportKeyPair( BCRYPT_MLKEM_ALG_HANDLE, NULL, // hImportKey BCRYPT_MLKEM_ENCAPSULATION_BLOB, &hKeyPair, encapsulationKeyBlob.data(), static_cast<ULONG>(encapsulationKeyBlob.size()), 0)); // dwFlags // Get the secret key length and ciphertext length. These values are static // and can be cached for the algorithm handle. ULONG cbSecretKey = 0; ULONG cbProperty = sizeof(cbSecretKey);
O servidor usa BCryptGetProperty para obter o comprimento da chave secreta e o comprimento do texto criptografado e alocar os buffers necessários para ambos.
THROW_IF_NTSTATUS_FAILED( BCryptGetProperty( &hKeyPair, BCRYPT_KEM_SHARED_SECRET_LENGTH, reinterpret_cast<PUCHAR>(&cbSecretKey), cbProperty, &cbProperty, 0)); // dwFlags ULONG cbCipherText = 0; cbProperty = sizeof(cbCipherText); THROW_IF_NTSTATUS_FAILED( BCryptGetProperty( &hKeyPair, BCRYPT_KEM_CIPHERTEXT_LENGTH, reinterpret_cast<PUCHAR>(&cbCipherText), cbProperty, &cbProperty, 0)); // dwFlags // Then allocate the required buffers. vector<BYTE> secretKey(cbSecretKey); vector<BYTE> cipherText(cbCipherText);
O servidor executa BCryptEncapsulate e envia o texto criptografado para o cliente na mensagem de troca de chaves do servidor.
// Perform the encapsulate operation. THROW_IF_NTSTATUS_FAILED( BCryptEncapsulate( hKeyPair.get(), secretKey.data(), static_cast<ULONG>(secretKey.size()), &cbSecretKey, cipherText.data(), static_cast<ULONG>(cipherText.size()), &cbCipherText, 0)); // dwFlags // cipherText is sent to the client in the server's key_exchange message. SendToClient(cipherText.data(), cipherText.size());
O cliente usa a troca de chaves de servidor recebida para gerar um texto criptografado que encapsula um segredo compartilhado.
// pbEncapsulationKey is sent on the wire in the client's key_exchange // message. // ... // < It's now the server's turn. It will use the encapsulation key to // generate the a CipherText encapsulating the shared secret key and send // it as a response to the client's key_exchange message. Sample_Server() // demonstrates how a hypothetical server may do this.> // ... // When the ServerKeyExchange message is received from the TLS server, // get the ML-KEM CipherText from the ServerKeyExchange message. vector<BYTE> cipherText = GetServerKeyExchange();
O cliente chama BCryptGetProperty para obter o comprimento da chave secreta e alocar o buffer apropriado.
// Get the secret key length. This value is static and can be cached for // the algorithm handle. ULONG cbSecretKey = 0; ULONG cbProperty = sizeof(cbSecretKey); THROW_IF_NTSTATUS_FAILED( BCryptGetProperty( &hKeyPair, BCRYPT_KEM_SHARED_SECRET_LENGTH, reinterpret_cast<PUCHAR>(&cbSecretKey), cbProperty, &cbProperty, 0)); // dwFlags
O cliente constrói a chave secreta compartilhada criando um blob de chaves e chamando BCryptDecapsulate com o texto codificado e o comprimento do segredo.
vector<BYTE> secretKey(cbSecretKey); THROW_IF_NTSTATUS_FAILED( BCryptDecapsulate( hKeyPair.get(), cipherText.data(), static_cast<ULONG>(cipherText.size()), secretKey.data(), static_cast<ULONG>(secretKey.size()), &cbSecretKey, 0)); // dwFlags
Derivando chaves de sessão
O cliente e o servidor agora têm o mesmo segredo compartilhado, que pode ser passado para uma função de derivação de chave, como DeriveSessionKeys , para gerar chaves de sessão para uma comunicação segura.
// secretKey contains the shared secret key which plugs into the TLS key
// schedule.
DeriveSessionKeys(secretKey);
Examinar o exemplo de código completo
Você pode examinar o exemplo de código completo abaixo:
void Sample_Client()
{
// Generate the key pair for key exchange
unique_bcrypt_key hKeyPair;
THROW_IF_NTSTATUS_FAILED(
BCryptGenerateKeyPair(
BCRYPT_MLKEM_ALG_HANDLE,
&hKeyPair,
0, // dwLength
0)); // dwFlags
THROW_IF_NTSTATUS_FAILED(
BCryptSetProperty(
&hKeyPair,
BCRYPT_PARAMETER_SET_NAME,
(PUCHAR) BCRYPT_MLKEM_PARAMETER_SET_768,
sizeof(BCRYPT_MLKEM_PARAMETER_SET_768),
0)); // dwFlags
THROW_IF_NTSTATUS_FAILED(
BCryptFinalizeKeyPair(
hKeyPair.get(),
0)); // dwFlags
ULONG cbEncapsulationKeyBlob = 0;
THROW_IF_NTSTATUS_FAILED(
BCryptExportKey(
hKeyPair.get(),
NULL,
BCRYPT_MLKEM_ENCAPSULATION_BLOB,
NULL, // pbOutput
0, // cbOutput
&cbEncapsulationKeyBlob,
0)); // dwFlags
vector<BYTE> encapsulationKeyBlob(cbEncapsulationKeyBlob);
THROW_IF_NTSTATUS_FAILED(
BCryptExportKey(
hKeyPair.get(),
NULL,
BCRYPT_MLKEM_ENCAPSULATION_BLOB,
encapsulationKeyBlob.data(),
static_cast<ULONG>(encapsulationKeyBlob.size()),
&cbEncapsulationKeyBlob,
0));
BCRYPT_MLKEM_KEY_BLOB* pEncapsulationKeyBlob =
reinterpret_cast<BCRYPT_MLKEM_KEY_BLOB *>(encapsulationKeyBlob.data());
ASSERT(pEncapsulationKeyBlob->dwMagic == BCRYPT_MLKEM_PUBLIC_MAGIC);
ASSERT(pEncapsulationKeyBlob->cbParameterSet == sizeof(BCRYPT_MLKEM_PARAMETER_SET_768));
if (wcscmp(BCRYPT_MLKEM_PARAMETER_SET_768, reinterpret_cast<WCHAR *>(pEncapsulationKeyBlob + 1)) != 0)
{
return;
}
PBYTE pbEncapsulationKey = reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768);
ULONG cbEncapsulationKey = pEncapsulationKeyBlob->cbKey;
SendToServer(pbEncapsulationKey, cbEncapsulationKey);
// pbEncapsulationKey is sent on the wire in the client's key_exchange
// message.
// ...
// < It's now the server's turn. It will use the encapsulation key to
// generate the a CipherText encapsulating the shared secret key and send
// it as a response to the client's key_exchange message. Sample_Server()
// demonstrates how a hypothetical server may do this.>
// ...
// When the ServerKeyExchange message is received from the TLS server,
// get the ML-KEM CipherText from the ServerKeyExchange message.
vector<BYTE> cipherText = GetServerKeyExchange();
// Get the secret key length. This value is static and can be cached for
// the algorithm handle.
ULONG cbSecretKey = 0;
ULONG cbProperty = sizeof(cbSecretKey);
THROW_IF_NTSTATUS_FAILED(
BCryptGetProperty(
&hKeyPair,
BCRYPT_KEM_SHARED_SECRET_LENGTH,
reinterpret_cast<PUCHAR>(&cbSecretKey),
cbProperty,
&cbProperty,
0)); // dwFlags
vector<BYTE> secretKey(cbSecretKey);
THROW_IF_NTSTATUS_FAILED(
BCryptDecapsulate(
hKeyPair.get(),
cipherText.data(),
static_cast<ULONG>(cipherText.size()),
secretKey.data(),
static_cast<ULONG>(secretKey.size()),
&cbSecretKey,
0)); // dwFlags
// secretKey is the shared secret key which plugs into the TLS key
// schedule.
DeriveSessionKeys(secretKey);
}
void Sample_Server()
{
// Server receives the client's key_exchange message and retrieves the
// encapsulation key bytes.
vector<BYTE> encapsulationKey = GetClientKeyExchange();
ULONG cbEncapsulationKey = static_cast<ULONG>(encapsulationKey.size());
// Put the Key in a BCRYPT_KEY_BLOB and import it.
ULONG cbEncapsulationKeyBlob = sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768) + cbEncapsulationKey;
vector<BYTE> encapsulationKeyBlob(cbEncapsulationKeyBlob);
BCRYPT_MLKEM_KEY_BLOB* pEncapsulationKeyBlob =
reinterpret_cast<BCRYPT_MLKEM_KEY_BLOB *>(encapsulationKeyBlob.data());
pEncapsulationKeyBlob->dwMagic = BCRYPT_MLKEM_PUBLIC_MAGIC;
pEncapsulationKeyBlob->cbParameterSet = sizeof(BCRYPT_MLKEM_PARAMETER_SET_768);
pEncapsulationKeyBlob->cbKey = cbEncapsulationKey;
CopyMemory(
reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB),
BCRYPT_MLKEM_PARAMETER_SET_768,
sizeof(BCRYPT_MLKEM_PARAMETER_SET_768));
CopyMemory(
reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768),
encapsulationKey.data(),
encapsulationKey.size());
unique_bcrypt_key hKeyPair;
// The server knows the ML-KEM parameter set from the client's
// key_exchange, which denotes the parameter set associated with the
// encapsulation key it sent. In this case, we know it's
// BCRYPT_MLKEM_PARAMETER_SET_768.
THROW_IF_NTSTATUS_FAILED(
BCryptImportKeyPair(
BCRYPT_MLKEM_ALG_HANDLE,
NULL, // hImportKey
BCRYPT_MLKEM_ENCAPSULATION_BLOB,
&hKeyPair,
encapsulationKeyBlob.data(),
static_cast<ULONG>(encapsulationKeyBlob.size()),
0)); // dwFlags
// Get the secret key length and ciphertext length. These values are static
// and can be cached for the algorithm handle.
ULONG cbSecretKey = 0;
ULONG cbProperty = sizeof(cbSecretKey);
THROW_IF_NTSTATUS_FAILED(
BCryptGetProperty(
&hKeyPair,
BCRYPT_KEM_SHARED_SECRET_LENGTH,
reinterpret_cast<PUCHAR>(&cbSecretKey),
cbProperty,
&cbProperty,
0)); // dwFlags
ULONG cbCipherText = 0;
cbProperty = sizeof(cbCipherText);
THROW_IF_NTSTATUS_FAILED(
BCryptGetProperty(
&hKeyPair,
BCRYPT_KEM_CIPHERTEXT_LENGTH,
reinterpret_cast<PUCHAR>(&cbCipherText),
cbProperty,
&cbProperty,
0)); // dwFlags
// Then allocate the required buffers.
vector<BYTE> secretKey(cbSecretKey);
vector<BYTE> cipherText(cbCipherText);
// Perform the encapsulate operation.
THROW_IF_NTSTATUS_FAILED(
BCryptEncapsulate(
hKeyPair.get(),
secretKey.data(),
static_cast<ULONG>(secretKey.size()),
&cbSecretKey,
cipherText.data(),
static_cast<ULONG>(cipherText.size()),
&cbCipherText,
0)); // dwFlags
// cipherText is sent to the client in the server's key_exchange message.
SendToClient(cipherText.data(), cipherText.size());
// secretKey contains the shared secret key which plugs into the TLS key
// schedule.
DeriveSessionKeys(secretKey);
}
Conteúdo relacionado
- Usando ML-DSA com CNG
- Visão geral da programação CNG típica
- Funções de armazenamento de chaves CNG