Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga / Notatka
Niektóre informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany przed jego wydaniem komercyjnym. Firma Microsoft nie udziela żadnych gwarancji, wyraźnych ani domniemanych, w odniesieniu do podanych tutaj informacji. Funkcja opisana w tym temacie jest dostępna w wersjach wstępnych programu Windows Insider Preview.
Ten artykuł zawiera przewodnik dotyczący implementowania kompleksowego przepływu pracy na potrzeby przeprowadzania wymiany kluczy przy użyciu algorytmu Module-Lattice-Based Key Encapsulation Mechanism (ML-KEM) z interfejsem API CNG firmy Microsoft.
Przykładowy kod hermetyzacji i decapsulacji klucza ML-KEM przy użyciu powłoki BCrypt
ML-KEM to algorytm post-kwantowy używany do wymiany kluczy i jest ustandaryzowany w programie FIPS 203. Wymiana kluczy jest ważną częścią protokołów zabezpieczeń, takich jak TLS, gdzie klient i serwer negocjują połączenie oraz tworzą i udostępniają materiał klucza do szyfrowania i odszyfrowywania wiadomości wysyłanych przez Internet. W modelu KEM klucze generowane w procesie generowania par kluczy są nazywane kluczem hermetyzacji i kluczem decapsulacji. Klucz enkapsulacji jest publiczny i może być używany przez każdego, kto wykonuje operację enkapsulacji klucza, która generuje klucz tajny (dla strony wykonującej tę operację) oraz tekst zaszyfrowany. Szyfrogram jest dostarczany jako dane wejściowe do operacji dekapsulacji klucza przez właściciela klucza prywatnego w celu odzyskania tego samego wspólnego klucza tajnego, który strona kapsulująca uzyskała podczas procesu kapsulacji klucza. W tym przykładzie pokazano, jak hipotetyczna aplikacja klienta i serwera TLS używa nowych interfejsów API ML-KEM w usłudze BCrypt w celu przeprowadzenia wymiany kluczy.
Konfiguracja i generowanie par kluczy
W poniższych krokach opisano proces konfigurowania generowania i hermetyzacji par kluczy ML-KEM:
Użyj elementu BCryptGenerateKeyPair z BCRYPT_MLKEM_ALG_HANDLE , aby utworzyć nową parę kluczy na potrzeby hermetyzacji kluczy. Pola długości i flag są oba równe
0
, ponieważ długości kluczy są definiowane przez zestaw parametrów 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
Wywołaj BCryptSetProperty dla pary kluczy, aby ustawić BCRYPT_PARAMETER_SET_NAME na BCRYPT_MLKEM_PARAMETER_SET_768, określając zestaw parametrów dla operacji ML-KEM. ML-KEM obsługuje również zestawy parametrów 512 i 1024 zdefiniowane przez 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
Wywołaj BCryptFinalizeKeyPair, aby przygotować parę kluczy do używania w kolejnych operacjach.
THROW_IF_NTSTATUS_FAILED( BCryptFinalizeKeyPair( hKeyPair.get(), 0)); // dwFlags
Eksportowanie kluczy publicznych i wymiana
Wywołaj funkcję BCryptExportKey z buforem wyjściowym
NULL
, aby sprawdzić wymagany rozmiar do wyeksportowania 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
Przydziel bufor na podstawie wcześniej pobranego rozmiaru, a następnie wyeksportuj klucz hermetyzacji (publiczny) przy użyciu klucza BCryptExportKey. Ten obiekt blob zostanie wysłany do partnera wymiany kluczy (np. serwera w scenariuszu klient-serwer).
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));
Upewnij się , że BCRYPT_MLKEM_KEY_BLOB ma prawidłowy publiczny zestaw magiczny i 768 parametrów.
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; }
Wyślij klucz hermetyzacji do serwera w komunikacie wymiany klucza klienta.
PBYTE pbEncapsulationKey = reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768); ULONG cbEncapsulationKey = pEncapsulationKeyBlob->cbKey; SendToServer(pbEncapsulationKey, cbEncapsulationKey);
Hermetyzacja i decapsulation
W poniższych krokach opisano proces hermetyzacji i decapsulatowania wspólnego klucza tajnego:
Serwer odbiera komunikat wymiany kluczy klienta i pobiera bajty klucza hermetyzacji.
// 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());
Serwer umieszcza klucz w BCRYPT_KEY_BLOB przy użyciu zestawu parametrów 768 i publicznej magii, a następnie importuje klucz hermetyzacji.
// 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);
Serwer używa BCryptGetProperty, aby uzyskać długość klucza sekretu i długość szyfrogramu oraz przydzielić wymagane bufory dla obu.
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);
Serwer wykonuje funkcję BCryptEncapsulate i wysyła do klienta tekst szyfrowania w komunikacie wymiany klucza serwera.
// 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());
Klient używa otrzymanej wymiany klucza serwera do wygenerowania szyfrogramu, który obejmuje wspólny sekret.
// 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();
Klient wywołuje BCryptGetProperty, aby uzyskać długość klucza sekretnego i przydzielić odpowiedni bufor.
// 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
Klient konstruuje wspólny klucz tajny, tworząc blob klucza i wywołując metodę BCryptDecapsulate z tekstem zaszyfrowanym i długością klucza tajnego.
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
Wyprowadzanie kluczy sesji
Zarówno klient, jak i serwer mają teraz ten sam wspólny klucz tajny, który można przekazać do funkcji wyprowadzania kluczy, takiej jak DeriveSessionKeys w celu wygenerowania kluczy sesji na potrzeby bezpiecznej komunikacji.
// secretKey contains the shared secret key which plugs into the TLS key
// schedule.
DeriveSessionKeys(secretKey);
Zapoznaj się z pełnym przykładem kodu
Pełny przykładowy kod można przejrzeć poniżej:
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);
}