次の方法で共有


CNG での ML-KEM の使用

一部の情報は、市販される前に大幅に変更される可能性があるプレリリース製品に関連しています。 Microsoft は、ここで提供される情報に関して明示的または黙示的な保証を行いません。 このトピックで説明する機能は、Windows Insider Previewのプレリリース バージョンで使用できます。

この記事では、Microsoft の CNG API を使用して Module-Lattice-Based Key Encapsulation Mechanism (ML-KEM) アルゴリズムを使用してキー交換を実行するためのエンドツーエンドのワークフローを実装するためのガイドを提供します。

BCrypt を使用した ML-KEM キーカプセル化とカプセル化解除のコードのサンプル

ML-KEM はキー交換に使用されるポスト量子アルゴリズムであり、 FIPS 203 で標準化されています。 キー交換は、TLS などのセキュリティ プロトコルの重要な部分です。これにより、クライアントとサーバーは接続をネゴシエートし、インターネット経由で送信されたメッセージを暗号化および復号化するためのキー マテリアルを作成して共有します。 KEM では、キー ペア生成プロセスで生成されるキーはカプセル化キーとカプセル化解除キーと呼ばれます。 カプセル化キーは公開されており、(この操作を実行するパーティの) 秘密鍵と暗号テキストを生成するキー カプセル化操作を実行するために、だれでも使用できます。 暗号化テキストは、キー カプセル化プロセス中にカプセル化側が取得したのと同じ共有秘密キーを回復するために、秘密キー所有者によってキーのカプセル化解除操作への入力として提供されます。 この例では、架空の TLS クライアントとサーバー アプリケーションが BCrypt の新しい ML-KEM API を使用してキー交換を実行する方法を示します。

セットアップとキー ペアの生成

次の手順では、ML-KEM キー ペアの生成とカプセル化を設定するプロセスについて説明します。

  1. BCRYPT_MLKEM_ALG_HANDLEBCryptGenerateKeyPair を使用して、キーカプセル化用の新しいキー ペアを作成します。 キー長は ML-KEM パラメーター・セットによって定義されるため、長さフィールドとフラグ・フィールドの両方が 0されます。

    // 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. キー ペアで BCryptSetProperty を呼び出して 、BCRYPT_PARAMETER_SET_NAME を BCRYPT_MLKEM_PARAMETER_SET_768 に設定し、 ML-KEM 操作に設定されたパラメーターを指定します。 ML-KEM では、NIST によって定義された 512 および 1024 パラメーター セットもサポートされます。

    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. BCryptFinalizeKeyPair を呼び出して、後続の操作でキー ペアを使用できる状態にします。

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

公開キーのエクスポートと交換

  1. 出力バッファーを使用して NULL を呼び出し、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. 前に取得したサイズに基づいてバッファーを割り当て、 BCryptExportKey を使用してカプセル化 (公開) キーをエクスポートします。 この BLOB は、キー交換パートナー (たとえば、クライアント とサーバーのシナリオのサーバー) に送信されます。

    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. BCRYPT_MLKEM_KEY_BLOBに正しいパブリック マジックと 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. クライアント キー交換メッセージでカプセル化キーをサーバーに送信します。

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

カプセル化とカプセル化解除

次の手順では、共有秘密キーをカプセル化およびカプセル化解除するプロセスについて説明します。

  1. サーバーは、クライアントのキー交換メッセージを受信し、カプセル化キーのバイトを取得します。

    // 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. サーバーは、768 パラメーター セットとパブリック マジックを使用して キーをBCRYPT_KEY_BLOB に入れ、カプセル化キーをインポートします。

    // 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. サーバーは BCryptGetProperty を使用して秘密鍵の長さと暗号テキストの長さを取得し、両方に必要なバッファーを割り当てます。

    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. サーバーは BCryptEncapsulate を実行し、サーバー キー交換メッセージでクライアントに暗号テキストを送信します。

    // 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. クライアントは、受信したサーバー キー交換を使用して、共有シークレットをカプセル化する暗号テキストを生成します。

    // 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. クライアントは BCryptGetProperty を呼び出して秘密鍵の長さを取得し、適切なバッファーを割り当てます。

    // 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. クライアントは、キー BLOB を作成し、暗号テキストとシークレットの長さで BCryptDecapsulate を呼び出すことによって、共有シークレット キーを構築します。

    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
    

セッション キーの派生

クライアントとサーバーの両方に同じ共有シークレットが追加されました。これにより、 DerivSessionKeys などのキー派生関数に渡して、セキュリティで保護された通信用のセッション キーを生成できます。

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

完全なコード サンプルを確認する

以下の完全なコード サンプルを確認できます。

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