Compartilhar via


Usando ML-KEM com CNG

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:

  1. 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
    
  2. 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
    
  3. 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

  1. 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
    
  2. 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));
    
  3. 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;
    }
    
  4. 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:

  1. 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());
    
  2. 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);
    
  3. 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);
    
  4. 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());
    
  5. 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(); 
    
  6. 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
    
  7. 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);
}