Megosztás a következőn keresztül:


A ML-KEM használata CNG esetében.

Megjegyzés:

Egyes információk egy előzetes termékre vonatkoznak, amely jelentősen módosítható a kereskedelmi forgalomba kerülés előtt. A Microsoft nem vállal kifejezett vagy hallgatólagos szavatosságot az itt megadott információkra vonatkozóan. A jelen témakörben ismertetett funkció a Windows Insider Előzetes verzióelőzetes verzióiban érhető el.

Ez a cikk útmutatót nyújt a teljes körű munkafolyamat megvalósításához a kulcscserék végrehajtásához, a Microsoft CNG API-jával és aLattice-Based kulcsbeágyazási mechanizmus (ML-KEM) algoritmus használatával.

Minta ML-KEM kulcs kapszulázási és dekapszulázási mintakód a BCrypt használatával

ML-KEM egy kulcscseréhez használt poszt-kvantum algoritmus, és a FIPS 203-ban szabványosított. A kulcscsere az olyan biztonsági protokollok fontos része, mint a TLS, amelynek során az ügyfél és a kiszolgáló egyeztet egy kapcsolatot, és kulcsanyagokat hoz létre és oszt meg az interneten keresztül küldött üzenetek titkosításához és visszafejtéséhez. A KEM-ben a kulcspárok generálási folyamatában előállított kulcsokat kapszulázó kulcsnak és dekapszuláló kulcsnak nevezik. A beágyazási kulcs nyilvános, és bárki használhatja kulcsbefoglalási művelet végrehajtására, amely titkos kulcsot (a műveletet végrehajtó fél számára) és egy rejtjelszöveget hoz létre. A titkosítási szöveget a titkos kulcs tulajdonosa adja meg a kulcsfejlelési művelet bemeneteként, hogy helyreállítsa azt a közös titkos kulcsot, amelyet a beágyazó fél a kulcsbefoglalási folyamat során szerzett be. Ez a példa bemutatja, hogy egy hipotetikus TLS-ügyfél- és kiszolgálóalkalmazás hogyan használja fel az új ML-KEM API-kat a BCryptben a kulcscsere végrehajtásához.

Beállítás és kulcspárok létrehozása

Az alábbi lépések ismertetik a ML-KEM kulcspárok létrehozásának és beágyazásának folyamatát:

  1. A BCryptGenerateKeyPair és a BCRYPT_MLKEM_ALG_HANDLE használatával hozzon létre egy új kulcspárt a kulcsok beágyazásához. A hosszúság és a zászlók mezők egyaránt 0, mivel a kulcshosszokat a ML-KEM paraméterkészlet határozza meg.

    // 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. Hívja meg a BCryptSetProperty függvényt a kulcspáron, hogy beállítsa a BCRYPT_PARAMETER_SET_NAME értékét a BCRYPT_MLKEM_PARAMETER_SET_768 értékre, így meghatározva a ML-KEM művelet paraméterkészletét. ML-KEM az NIST által definiált 512- és 1024-s paraméterkészleteket is támogatja.

    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. Hívja meg a BCryptFinalizeKeyPair parancsot, hogy a kulcspár készen álljon a későbbi műveletekben való használatra.

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

Nyilvános kulcs exportálása és cseréje

  1. A BCryptExportKey hívása kimeneti NULL pufferrel a BCRYPT_MLKEM_ENCAPSULATION_BLOB exportálásához szükséges méret lekérdezéséhez.

    ULONG cbEncapsulationKeyBlob = 0;
    
    THROW_IF_NTSTATUS_FAILED(
        BCryptExportKey(
            hKeyPair.get(),
            NULL,
            BCRYPT_MLKEM_ENCAPSULATION_BLOB,
            NULL, // pbOutput
            0, // cbOutput
            &cbEncapsulationKeyBlob,
            0)); // dwFlags
    
  2. Foglaljon le egy puffert a korábban lekért méret alapján, majd exportálja a beágyazási (nyilvános) kulcsot a BCryptExportKey használatával. Ezt a blobot a rendszer elküldi a kulcscserepartnernek (például egy ügyfél-kiszolgáló forgatókönyvben lévő kiszolgálónak).

    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. Győződjön meg arról, hogy a BCRYPT_MLKEM_KEY_BLOB rendelkezik a megfelelő nyilvános varázslattal és 768 paraméterkészlettel.

    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. Küldje el a beágyazási kulcsot a kiszolgálónak az ügyfélkulcs-csereüzenetben.

    PBYTE pbEncapsulationKey = reinterpret_cast<PBYTE>(pEncapsulationKeyBlob) + sizeof(BCRYPT_MLKEM_KEY_BLOB) + sizeof(BCRYPT_MLKEM_PARAMETER_SET_768);
    ULONG cbEncapsulationKey = pEncapsulationKeyBlob->cbKey;
    SendToServer(pbEncapsulationKey, cbEncapsulationKey);
    

Beágyazás és Kibontás

Az alábbi lépések a megosztott titkos kulcs beágyazásának és lefejezésének folyamatát ismertetik:

  1. A kiszolgáló megkapja az ügyfél kulcscsere üzenetét, és lekéri a beágyazási kulcs bájtjait.

    // 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. A kiszolgáló a kulcsot a 768 paraméterkészlet és egy nyilvános varázskód használatával egy BCRYPT_KEY_BLOB helyezi el, majd importálja a beágyazási kulcsot.

    // 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. A kiszolgáló A BCryptGetProperty használatával lekéri a titkos kulcs hosszát és a rejtjelszöveg hosszát, és lefoglalja a szükséges puffereket mindkettőhöz.

    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. A kiszolgáló végrehajtja a BCryptEncapsulate parancsot , és elküldi a rejtjelszöveget az ügyfélnek a kiszolgálókulcs-csereüzenetben.

    // 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. Az ügyfél a fogadott kiszolgálókulcs-cserével hoz létre egy titkosítószöveget, amely egy megosztott titkos kulcsot foglal magában.

    // 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. Az ügyfél meghívja a BCryptGetPropertyt a titkos kulcs hosszának lekéréséhez és a megfelelő puffer lefoglalásához.

    // 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. Az ügyfél egy kulcsblob létrehozásával és a BCryptDecapsulate meghívásával hozza létre a megosztott titkos kulcsot a titkosítási szöveggel és a titkos kód hosszával.

    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
    

Munkamenetkulcsok származtatása

Az ügyfél és a kiszolgáló is ugyanazzal a megosztott titkos kóddal rendelkezik, amelyet átadhat egy kulcslevezetési függvénynek, például a DeriveSessionKeys-nek , hogy munkamenetkulcsokat hozzon létre a biztonságos kommunikációhoz.

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

A teljes kódminta áttekintése

Az alábbi teljes kódmintát áttekintheti:

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