Diffie-Hellman 金鑰

這很重要

本文使用 Cryptography API: Next Generation (CNG),這是新Windows密碼學應用的推薦 API。 對於大多數新應用,建議使用橢 圓曲線 Diffie-Hellman(ECDH) 搭配標準命名曲線,如 P-256 或 P-384,這能提供同等或更強的安全性,且鍵數較短且參數管理負擔更低。

舊有的 CryptoAPI(CAPI1)函式(CryptGenKeyCryptExportKeyCryptAcquireContext、等等)已被棄用。 不要在新應用中使用它們。

產生 Diffie-Hellman 金鑰

要使用 CNG 產生 Diffie-Hellman 金鑰對,請執行以下步驟:

  1. 呼叫 BCryptOpenAlgorithmProviderBCRYPT_DH_ALGORITHM,取得演算法提供者的帳號。

  2. 呼叫 BCryptGenerateKeyPair 建立金鑰對,並指定金鑰大小(以位元為單位)。 至少使用 2048 位元以確保足夠安全;512 位元金鑰(如舊版 CAPI1 範例所用)在密碼學上較弱,且不得在新程式碼中使用。

  3. 在呼叫 BCryptFinalizeKeyPair 之前,先使用屬性 BCRYPT_DH_PARAMETERS 呼叫 BCryptSetProperty 設定 DH 參數(質數 P 與生成元 G)。 屬性值必須是 BCRYPT_DH_PARAMETER_HEADER 結構,其後緊接著是 P 值,然後是 G 值,每個值的長度為 cbKeyLength 位元組,並以大端序排列。

    雙方必須使用相同的 PG 值。 對於新程式碼,請使用知名的標準化群組,而非產生自訂參數——例如, RFC 3526 中的 2048 位元 MODP Group 14(如下範例所用)在安全性與相容性之間提供了良好的平衡。 BCRYPT_DH_PUBLIC_BLOB匯出格式包含 PG,因此當雙方分別使用不同機器或程序時,接收方仍能從收到的 blob 中提取這些資料。 在一個自包含的例子中,雙方共用同一進程,可以直接重複使用相同的參數二進位大物件。

  4. 請致電 BCryptFinalizeKeyPair 以完成金鑰產生。 必須先呼叫此函式,才能使用或匯出金鑰。

  5. 當金鑰不再需要時,呼叫 BCryptDestroyKey 來釋放金鑰句柄,然後呼叫 BCryptCloseAlgorithmProvider 來釋放提供者的句柄。

交換 Diffie-Hellman 金鑰

Diffie-Hellman 演算法的目的是讓兩個或多個方能夠透過不安全的網路分享資訊,創造並共享相同的祕密值。 透過網路分享的資訊是雙方的 Diffie-Hellman 公開金鑰。 兩個金鑰交換者所使用的程式如下所示:

  • 雙方同意 Diffie-Hellman 參數:質數(P)和生成元數(G)。
  • 一方1將其 Diffie-Hellman 公鑰傳送給方2。
  • 方 2 利用自己的私鑰和方 1 的公鑰計算共享秘密。
  • 方2將其 Diffie-Hellman 公鑰傳送給方1。
  • 第一方利用自己的私鑰和第二方的公鑰計算共享秘密。
  • 雙方現在擁有相同的共享秘密,可用來推導對稱加密金鑰。

準備一 Diffie-Hellman 公開金鑰以進行傳輸:

  1. 產生並完成金鑰對後,呼叫 BCryptExportKey ,以 BCRYPT_DH_PUBLIC_BLOB 作為 blob 類型,以獲得公鑰位元組。 blob 包含 PG 及公鑰 Y 值,皆以大端位元組順序排列。

  2. 透過網路將這些位元組傳送給對方。

備註

CNG DH 塊狀(BCRYPT_DH_PUBLIC_BLOBBCRYPT_DH_PRIVATE_BLOB) 中的關鍵材料以 大端序 排列。 這與已棄用的 CryptoAPI(CAPI1)所使用的小端格式相反。 使用CAP1編碼的金鑰材料時,請特別小心。

匯入 Diffie-Hellman 公開金鑰並導出共享密鑰:

  1. 呼叫 BCryptImportKeyPairBCRYPT_DH_PUBLIC_BLOB 匯入對方的公鑰。 這需要一個使用BCRYPT_DH_ALGORITHM開啟的演算法提供者的控制代碼(handle)。

  2. 使用你自己的私鑰控制碼和匯入的公鑰控制碼來呼叫BCryptSecretAgreement。 這會產生一個代表原始共享秘密值的 秘密協議 句柄,(Y^X) mod P。

  3. 呼叫 BCryptDeriveKey ,從共享秘密中推導出可用的金鑰材料。 使用適合您情境的金鑰推導函數(KDF);BCRYPT_KDF_HASH 搭配 SHA-256 是合適的通用選擇。

  4. 利用導出的金鑰位元組來構建對稱金鑰(例如,將 BCryptGenerateSymmetricKey 命名為 BCRYPT_AES_ALGORITHM),以便後續加密或解密。

  5. 完成後,呼叫 BCryptDestroySecret 以釋放秘密約定的 handle,並呼叫 BCryptDestroyKey 以釋放所有金鑰的 handle。

匯出 Diffie-Hellman 私鑰

謹慎

匯出私鑰是一項安全敏感的操作。 只有在絕對必要時才匯出私鑰資料,並妥善保護。 對於儲存在金鑰儲存提供者(KSP)中的金鑰,提供者可能會根據金鑰政策限制匯出。

若要將 Diffie-Hellman 私鑰匯出為記憶體 blob,請呼叫 BCryptExportKey 函式,搭配參數 。 所得的 blob 包含 一個 BCRYPT_DH_KEY_BLOB 標頭,後面是 PG、公開 Y 和私有 X 值,每個值皆以大端序排列。

若要稍後匯入私鑰,請呼叫 BCryptImportKeyPairBCRYPT_DH_PRIVATE_BLOB

範例程序代碼

以下範例展示了兩方使用 CNG 進行 Diffie-Hellman 金鑰交換。 雙方從共享秘密中推導出相同的金鑰材料,並比較衍生出的位元組。

備註

此範例使用顯式 Diffie-Hellman 參數,並透過使用 BCryptDeriveKeyBCRYPT_KDF_HASH從共享秘密中推導出金鑰材料。 在調整此範例時,請確保參數格式、金鑰衍生設定及所得金鑰使用符合您應用程式的安全與互通性需求。

#include <windows.h>
#include <bcrypt.h>
#include <stdio.h>
#include <cstring>
#pragma comment(lib, "bcrypt.lib")

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define CHECK(s, fn) if (!NT_SUCCESS(s)) { wprintf(L"Error in %s: 0x%08x\n", fn, s); goto cleanup; }

// 2048-bit MODP Group 14 prime (RFC 3526), big-endian.
// Uses the 2048-bit MODP Group 14 standardized prime.
static const BYTE g_Prime2048[] =
{
    0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0x0F,0xDA,0xA2,0x21,0x68,0xC2,0x34,
    0xC4,0xC6,0x62,0x8B,0x80,0xDC,0x1C,0xD1,0x29,0x02,0x4E,0x08,0x8A,0x67,0xCC,0x74,
    0x02,0x0B,0xBE,0xA6,0x3B,0x13,0x9B,0x22,0x51,0x4A,0x08,0x79,0x8E,0x34,0x04,0xDD,
    0xEF,0x95,0x19,0xB3,0xCD,0x3A,0x43,0x1B,0x30,0x2B,0x0A,0x6D,0xF2,0x5F,0x14,0x37,
    0x4F,0xE1,0x35,0x6D,0x6D,0x51,0xC2,0x45,0xE4,0x85,0xB5,0x76,0x62,0x5E,0x7E,0xC6,
    0xF4,0x4C,0x42,0xE9,0xA6,0x37,0xED,0x6B,0x0B,0xFF,0x5C,0xB6,0xF4,0x06,0xB7,0xED,
    0xEE,0x38,0x6B,0xFB,0x5A,0x89,0x9F,0xA5,0xAE,0x9F,0x24,0x11,0x7C,0x4B,0x1F,0xE6,
    0x49,0x28,0x66,0x51,0xEC,0xE4,0x5B,0x3D,0xC2,0x00,0x7C,0xB8,0xA1,0x63,0xBF,0x05,
    0x98,0xDA,0x48,0x36,0x1C,0x55,0xD3,0x9A,0x69,0x16,0x3F,0xA8,0xFD,0x24,0xCF,0x5F,
    0x83,0x65,0x5D,0x23,0xDC,0xA3,0xAD,0x96,0x1C,0x62,0xF3,0x56,0x20,0x85,0x52,0xBB,
    0x9E,0xD5,0x29,0x07,0x70,0x96,0x96,0x6D,0x67,0x0C,0x35,0x4E,0x4A,0xBC,0x98,0x04,
    0xF1,0x74,0x6C,0x08,0xCA,0x18,0x21,0x7C,0x32,0x90,0x5E,0x46,0x2E,0x36,0xCE,0x3B,
    0xE3,0x9E,0x77,0x2C,0x18,0x0E,0x86,0x03,0x9B,0x27,0x83,0xA2,0xEC,0x07,0xA2,0x8F,
    0xB5,0xC5,0x5D,0xF0,0x6F,0x4C,0x52,0xC9,0xDE,0x2B,0xCB,0xF6,0x95,0x58,0x17,0x18,
    0x39,0x95,0x49,0x7C,0xEA,0x95,0x6A,0xE5,0x15,0xD2,0x26,0x18,0x98,0xFA,0x05,0x10,
    0x15,0x72,0x8E,0x5A,0x8A,0xAC,0xAA,0x68,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};

// Generator for MODP Group 14 (g = 2), big-endian, zero-padded to 256 bytes.
static BYTE g_Generator2048[256] = { 0 };  // initialized to zero; set g_Generator2048[255] = 2 below

#define KEY_SIZE_BITS  2048
#define KEY_SIZE_BYTES (KEY_SIZE_BITS / 8)

int wmain()
{
    int ret = 1;

    // Set generator value (g = 2)
    g_Generator2048[KEY_SIZE_BYTES - 1] = 2;

    NTSTATUS status;
    BCRYPT_ALG_HANDLE hAlg1 = NULL, hAlg2 = NULL;
    BCRYPT_KEY_HANDLE hKey1 = NULL, hKey2 = NULL;
    BCRYPT_KEY_HANDLE hPubKey1 = NULL, hPubKey2 = NULL;
    BCRYPT_SECRET_HANDLE hSecret1 = NULL, hSecret2 = NULL;
    PBYTE pbPubBlob1 = NULL, pbPubBlob2 = NULL;
    PBYTE pbParams = NULL;
    PBYTE pbDerivedKey1 = NULL, pbDerivedKey2 = NULL;
    ULONG cbPubBlob1 = 0, cbPubBlob2 = 0;
    ULONG cbDerivedKey = 0;

    // Build the BCRYPT_DH_PARAMETERS blob: header + P + G (all big-endian).
    ULONG cbParams = sizeof(BCRYPT_DH_PARAMETER_HEADER) + 2 * KEY_SIZE_BYTES;
    pbParams = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbParams);
    if (!pbParams) { wprintf(L"Out of memory\n"); goto cleanup; }

    BCRYPT_DH_PARAMETER_HEADER* pHeader = (BCRYPT_DH_PARAMETER_HEADER*)pbParams;
    pHeader->cbLength    = cbParams;
    pHeader->dwMagic     = BCRYPT_DH_PARAMETERS_MAGIC;
    pHeader->cbKeyLength = KEY_SIZE_BYTES;
    memcpy(pbParams + sizeof(BCRYPT_DH_PARAMETER_HEADER),                   g_Prime2048,     KEY_SIZE_BYTES); // P
    memcpy(pbParams + sizeof(BCRYPT_DH_PARAMETER_HEADER) + KEY_SIZE_BYTES,  g_Generator2048, KEY_SIZE_BYTES); // G

    //
    // --- Party 1: generate key pair ---
    //
    status = BCryptOpenAlgorithmProvider(&hAlg1, BCRYPT_DH_ALGORITHM, NULL, 0);
    CHECK(status, L"BCryptOpenAlgorithmProvider (Party 1)");

    status = BCryptGenerateKeyPair(hAlg1, &hKey1, KEY_SIZE_BITS, 0);
    CHECK(status, L"BCryptGenerateKeyPair (Party 1)");

    status = BCryptSetProperty(hKey1, BCRYPT_DH_PARAMETERS, pbParams, cbParams, 0);
    CHECK(status, L"BCryptSetProperty BCRYPT_DH_PARAMETERS (Party 1)");

    status = BCryptFinalizeKeyPair(hKey1, 0);
    CHECK(status, L"BCryptFinalizeKeyPair (Party 1)");

    // Export Party 1's public key blob (includes P, G, Y).
    status = BCryptExportKey(hKey1, NULL, BCRYPT_DH_PUBLIC_BLOB, NULL, 0, &cbPubBlob1, 0);
    CHECK(status, L"BCryptExportKey size (Party 1)");

    pbPubBlob1 = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbPubBlob1);
    if (!pbPubBlob1) { wprintf(L"Out of memory\n"); goto cleanup; }

    status = BCryptExportKey(hKey1, NULL, BCRYPT_DH_PUBLIC_BLOB, pbPubBlob1, cbPubBlob1, &cbPubBlob1, 0);
    CHECK(status, L"BCryptExportKey (Party 1)");

    //
    // --- Party 2: generate key pair using same P and G ---
    //
    status = BCryptOpenAlgorithmProvider(&hAlg2, BCRYPT_DH_ALGORITHM, NULL, 0);
    CHECK(status, L"BCryptOpenAlgorithmProvider (Party 2)");

    status = BCryptGenerateKeyPair(hAlg2, &hKey2, KEY_SIZE_BITS, 0);
    CHECK(status, L"BCryptGenerateKeyPair (Party 2)");

    // Party 2 reuses the same DH parameters as Party 1.
    status = BCryptSetProperty(hKey2, BCRYPT_DH_PARAMETERS, pbParams, cbParams, 0);
    CHECK(status, L"BCryptSetProperty BCRYPT_DH_PARAMETERS (Party 2)");

    status = BCryptFinalizeKeyPair(hKey2, 0);
    CHECK(status, L"BCryptFinalizeKeyPair (Party 2)");

    // Export Party 2's public key blob.
    status = BCryptExportKey(hKey2, NULL, BCRYPT_DH_PUBLIC_BLOB, NULL, 0, &cbPubBlob2, 0);
    CHECK(status, L"BCryptExportKey size (Party 2)");

    pbPubBlob2 = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbPubBlob2);
    if (!pbPubBlob2) { wprintf(L"Out of memory\n"); goto cleanup; }

    status = BCryptExportKey(hKey2, NULL, BCRYPT_DH_PUBLIC_BLOB, pbPubBlob2, cbPubBlob2, &cbPubBlob2, 0);
    CHECK(status, L"BCryptExportKey (Party 2)");

    //
    // --- Party 1: import Party 2's public key, compute shared secret ---
    //
    status = BCryptImportKeyPair(hAlg1, NULL, BCRYPT_DH_PUBLIC_BLOB, &hPubKey2, pbPubBlob2, cbPubBlob2, 0);
    CHECK(status, L"BCryptImportKeyPair Party 2 public key (into Party 1)");

    status = BCryptSecretAgreement(hKey1, hPubKey2, &hSecret1, 0);
    CHECK(status, L"BCryptSecretAgreement (Party 1)");

    // Derive 32 bytes of key material using SHA-256.
    BCryptBufferDesc kdfParams = { 0 };
    BCryptBuffer kdfBuffer = { 0 };
    WCHAR szHashAlg[] = BCRYPT_SHA256_ALGORITHM;
    kdfBuffer.BufferType = KDF_HASH_ALGORITHM;
    kdfBuffer.cbBuffer   = sizeof(szHashAlg);
    kdfBuffer.pvBuffer   = szHashAlg;
    kdfParams.ulVersion  = BCRYPTBUFFER_VERSION;
    kdfParams.cBuffers   = 1;
    kdfParams.pBuffers   = &kdfBuffer;

    status = BCryptDeriveKey(hSecret1, BCRYPT_KDF_HASH, &kdfParams, NULL, 0, &cbDerivedKey, 0);
    CHECK(status, L"BCryptDeriveKey size (Party 1)");

    pbDerivedKey1 = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbDerivedKey);
    if (!pbDerivedKey1) { wprintf(L"Out of memory\n"); goto cleanup; }

    status = BCryptDeriveKey(hSecret1, BCRYPT_KDF_HASH, &kdfParams, pbDerivedKey1, cbDerivedKey, &cbDerivedKey, 0);
    CHECK(status, L"BCryptDeriveKey (Party 1)");

    //
    // --- Party 2: import Party 1's public key, compute shared secret ---
    //
    status = BCryptImportKeyPair(hAlg2, NULL, BCRYPT_DH_PUBLIC_BLOB, &hPubKey1, pbPubBlob1, cbPubBlob1, 0);
    CHECK(status, L"BCryptImportKeyPair Party 1 public key (into Party 2)");

    status = BCryptSecretAgreement(hKey2, hPubKey1, &hSecret2, 0);
    CHECK(status, L"BCryptSecretAgreement (Party 2)");

    pbDerivedKey2 = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbDerivedKey);
    if (!pbDerivedKey2) { wprintf(L"Out of memory\n"); goto cleanup; }

    ULONG cbDerivedKey2 = cbDerivedKey;
    status = BCryptDeriveKey(hSecret2, BCRYPT_KDF_HASH, &kdfParams, pbDerivedKey2, cbDerivedKey2, &cbDerivedKey2, 0);
    CHECK(status, L"BCryptDeriveKey (Party 2)");

    //
    // Verify both parties derived the same key material.
    //
    if (cbDerivedKey == cbDerivedKey2 && memcmp(pbDerivedKey1, pbDerivedKey2, cbDerivedKey) == 0)
    {
        wprintf(L"Success: both parties derived the same %u-byte key material.\n", cbDerivedKey);
        ret = 0;
    }
    else
    {
        wprintf(L"Error: derived keys do not match.\n");
    }

cleanup:
    if (pbDerivedKey2)  { SecureZeroMemory(pbDerivedKey2, cbDerivedKey); HeapFree(GetProcessHeap(), 0, pbDerivedKey2); }
    if (pbDerivedKey1)  { SecureZeroMemory(pbDerivedKey1, cbDerivedKey); HeapFree(GetProcessHeap(), 0, pbDerivedKey1); }
    if (hSecret2)       BCryptDestroySecret(hSecret2);
    if (hSecret1)       BCryptDestroySecret(hSecret1);
    if (hPubKey1)       BCryptDestroyKey(hPubKey1);
    if (hPubKey2)       BCryptDestroyKey(hPubKey2);
    if (pbPubBlob2)     HeapFree(GetProcessHeap(), 0, pbPubBlob2);
    if (pbPubBlob1)     HeapFree(GetProcessHeap(), 0, pbPubBlob1);
    if (hKey2)          BCryptDestroyKey(hKey2);
    if (hKey1)          BCryptDestroyKey(hKey1);
    if (hAlg2)          BCryptCloseAlgorithmProvider(hAlg2, 0);
    if (hAlg1)          BCryptCloseAlgorithmProvider(hAlg1, 0);
    if (pbParams)       HeapFree(GetProcessHeap(), 0, pbParams);

    return ret;
}