Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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:
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
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
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
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
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));
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; }
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:
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());
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);
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);
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());
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();
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
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);
}