Compartir a través de


Uso de ML-KEM con CNG

Nota:

Cierta información se relaciona con un producto de versión preliminar que puede modificarse sustancialmente antes de su publicación comercial. Microsoft no ofrece ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí. La característica descrita en este tema está disponible en versiones preliminares de Windows Insider Preview.

En este artículo se proporciona una guía para implementar el flujo de trabajo de un extremo a otro para realizar un intercambio de claves mediante el algoritmo Module-Lattice-Based Key Encapsulation Mechanism (ML-KEM) con la API de CNG de Microsoft.

Fragmento de código de encapsulación y desencapsulación de claves de ejemplo ML-KEM mediante BCrypt

ML-KEM es un algoritmo posterior al cuántico que se usa para el intercambio de claves y está estandarizado en FIPS 203. El intercambio de claves es una parte importante de los protocolos de seguridad, como TLS, en el que un cliente y un servidor negocian una conexión y crean y comparten material de clave para cifrar y descifrar mensajes enviados a través de Internet. En KEM, las claves generadas en el proceso de generación de pares de claves se denominan Clave de encapsulación y Clave de descapsulación. La clave de encapsulación es pública y cualquier persona puede usarla para realizar una operación de encapsulación de claves que genera una clave secreta (para la entidad que realiza esta operación) y un texto cifrado. El texto cifrado se proporciona como entrada para la operación de descapsulación de claves por parte del propietario de la clave privada para recuperar la misma clave secreta compartida que el usuario encapsulador ha obtenido durante el proceso de encapsulación de claves. En este ejemplo se muestra cómo una aplicación de servidor y cliente TLS hipotética consumiría las nuevas API de ML-KEM en BCrypt para realizar un intercambio de claves.

Configuración y generación de pares de claves

En los pasos siguientes se describe el proceso de configuración de la generación y encapsulación del par de claves de ML-KEM:

  1. Use BCryptGenerateKeyPair con BCRYPT_MLKEM_ALG_HANDLE para crear un nuevo par de claves para la encapsulación de claves. Los campos de longitud y banderas son 0, ya que las longitudes de clave se definen mediante el 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. Llame a BCryptSetProperty en el par de claves para establecer BCRYPT_PARAMETER_SET_NAME en BCRYPT_MLKEM_PARAMETER_SET_768, especificando el conjunto de parámetros para la operación ML-KEM. ML-KEM también admite los conjuntos de parámetros 512 y 1024 definidos por 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. Llame a BCryptFinalizeKeyPair para que el par de claves esté listo para su uso en operaciones posteriores.

    THROW_IF_NTSTATUS_FAILED(
        BCryptFinalizeKeyPair(
            hKeyPair.get(),
            0)); // dwFlags
    

Exportación y intercambio de claves públicas

  1. Llame a BCryptExportKey con un NULL búfer de salida para consultar el tamaño necesario para exportar el 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. Asigne un búfer basado en el tamaño recuperado anteriormente y, a continuación, exporte la clave de encapsulación (pública) mediante BCryptExportKey. Este blob se enviará al asociado de intercambio de claves (por ejemplo, el servidor en un escenario de 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. Asegúrese de que el BCRYPT_MLKEM_KEY_BLOB tiene la magia pública correcta y el 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. Envíe la clave de encapsulación al servidor en el mensaje de intercambio de claves de 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);
    

Encapsulación y descapsulación

En los pasos siguientes se describe el proceso de encapsular y descapsular la clave secreta compartida:

  1. El servidor recibe el mensaje de intercambio de claves del cliente y recupera los bytes de la clave de encapsulación.

    // 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. El servidor coloca la clave en un BCRYPT_KEY_BLOB con el conjunto de parámetros 768 y la magia pública, e importa la clave de encapsulación.

    // 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. El servidor usa BCryptGetProperty para obtener la longitud de clave secreta y la longitud del texto cifrado y asignar los búferes necesarios 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. El servidor realiza BCryptEncapsulate y envía el texto cifrado al cliente en el mensaje de intercambio de claves del 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. El cliente usa el intercambio de claves de servidor recibido para generar un texto cifrado que encapsula un secreto compartido.

    // 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. El cliente llama a BCryptGetProperty para obtener la longitud de la clave secreta y asignar el búfer adecuado.

    // 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. El cliente construye la clave secreta compartida mediante la creación de un blob de claves y la llamada a BCryptDecapsulate con el texto cifrado y la longitud del secreto.

    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
    

Derivación de claves de sesión

Tanto el cliente como el servidor ahora tienen el mismo secreto compartido, que se puede pasar a una función de derivación de claves como DeriveSessionKeys para generar claves de sesión para la comunicación segura.

    // secretKey contains the shared secret key which plugs into the TLS key
    // schedule.
    DeriveSessionKeys(secretKey);

Revisión del ejemplo de código completo

Puede revisar el ejemplo de código completo siguiente:

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