Dela via


Diffie-Hellman nycklar

Viktigt!

Den här artikeln använder api:et Cryptography: Next Generation (CNG), vilket är det rekommenderade API:et för nya Windows kryptografiska program. För de flesta nya program bör du överväga att använda Elliptic Curve Diffie-Hellman (ECDH) med en standardnamnkurva, till exempel P-256 eller P-384, som ger likvärdig eller starkare säkerhet med kortare nycklar och mindre omkostnader för parameterhantering.

De äldre funktionerna i CryptoAPI (CAPI1) (CryptGenKey, CryptExportKey, CryptAcquireContextoch så vidare) är inaktuella. Använd dem inte i nya program.

Generera Diffie-Hellman nycklar

Utför följande steg för att generera ett Diffie-Hellman nyckelpar med CNG:

  1. Anropa BCryptOpenAlgorithmProvider med BCRYPT_DH_ALGORITHM för att hämta ett algoritmproviderhandtag.

  2. Anropa BCryptGenerateKeyPair för att skapa nyckelparet och ange nyckelstorleken i bitar. Använd minst 2 048 bitar för tillräcklig säkerhet; 512-bitarsnycklar (som används i äldre CAPI1-exempel) är kryptografiskt svaga och får inte användas i ny kod.

  3. Ange DH-parametrar (prime P och generator G) genom att anropa BCryptSetProperty med BCRYPT_DH_PARAMETERS egenskapen innan du anropar BCryptFinalizeKeyPair. Egenskapsvärdet måste vara en BCRYPT_DH_PARAMETER_HEADER struktur följt omedelbart av P-värdet och sedan G-värdet , varje cbKeyLength byte i längd, i storslutsbyteordning.

    Båda parter måste använda samma P- och G-värden . För ny kod använder du en välkänd standardiserad grupp i stället för att generera anpassade parametrar – till exempel ger 2048-bitars MODP Group 14 från RFC 3526 (som används i exemplet nedan) en bra balans mellan säkerhet och kompatibilitet. Exportformatet BCRYPT_DH_PUBLIC_BLOB innehåller P och G, så att en mottagare kan extrahera dem från den mottagna bloben när de två parterna finns på separata datorer eller processer. I ett fristående exempel där båda parter delar samma process kan samma parameterblob återanvändas direkt.

  4. Anropa BCryptFinalizeKeyPair för att slutföra nyckelgenereringen. Den här funktionen måste anropas innan nyckeln kan användas eller exporteras.

  5. När nyckeln inte längre behövs anropar du BCryptDestroyKey för att släppa nyckelhandtaget och BCryptCloseAlgorithmProvider för att släppa providerhandtaget.

Utbyte av Diffie-Hellman-nycklar

Syftet med Diffie-Hellman-algoritmen är att göra det möjligt för två eller flera parter att skapa och dela ett identiskt hemligt värde genom att dela information via ett nätverk som inte är säkert. Den informationen som delas via nätverket är varje parts Diffie-Hellman offentliga nyckel. Processen som används av två nyckelutbytesparter är följande:

  • Båda parter är överens om Diffie-Hellman parametrar: ett primtal (P) och ett generatornummer (G).
  • Parti 1 skickar sin Diffie-Hellman offentliga nyckel till Parti 2.
  • Part 2 beräknar den delade hemligheten med hjälp av sin egen privata nyckel och part 1:s offentliga nyckel.
  • Parti 2 skickar sin Diffie-Hellman offentliga nyckel till parti 1.
  • Part 1 beräknar den delade hemligheten med hjälp av sin egen privata nyckel och part 2:s offentliga nyckel.
  • Båda parter har nu samma delade hemlighet, som kan användas för att härleda en symmetrisk krypteringsnyckel.

Så här förbereder du en offentlig Diffie-Hellman nyckel för överföring:

  1. När du har genererat och slutfört nyckelparet anropar du BCryptExportKey med BCRYPT_DH_PUBLIC_BLOB som blobtyp för att hämta offentliga nyckelbyte. Blobben innehåller värdena P, G och Y för offentlig nyckel, allt i storslutsbyteordning.

  2. Överför dessa byte till den andra parten via nätverket.

Anmärkning

Nyckelmaterial i CNG DH-blobbar (BCRYPT_DH_PUBLIC_BLOB, BCRYPT_DH_PRIVATE_BLOB) är i big-endian byteordning. Det här är motsatsen till little-endian-formatet som användes av den föråldrade CryptoAPI (CAPI1). Var försiktig när du samverkar med CAPI1-kodat nyckelmaterial.

Så här importerar du en Diffie-Hellman offentlig nyckel och härleder den delade hemligheten:

  1. Anropa BCryptImportKeyPair med BCRYPT_DH_PUBLIC_BLOB för att importera den andra partens offentliga nyckel. Detta kräver att ett algoritmproviderhandtag öppnas med BCRYPT_DH_ALGORITHM.

  2. Anropa BCryptSecretAgreement med ditt eget privata nyckelhandtag och det importerade offentliga nyckelhandtaget. Detta skapar ett hemligt avtalshandtag som representerar det råa delade hemlighetsvärdet (Y^X) mod P.

  3. Anropa BCryptDeriveKey för att härleda användbart nyckelmaterial från den delade hemligheten. Använd en nyckelhärledningsfunktion (KDF) som är lämplig för ditt scenario. BCRYPT_KDF_HASH är SHA-256 ett lämpligt val för generell användning.

  4. Använd byte för härledda nycklar för att konstruera en symmetrisk nyckel (till exempel anropa BCryptGenerateSymmetricKey med BCRYPT_AES_ALGORITHM) för efterföljande kryptering eller dekryptering.

  5. När du är klar anropar du BCryptDestroySecret för att släppa det hemliga avtalshandtaget och BCryptDestroyKey för att släppa alla nyckelhandtag.

Exportera en Diffie-Hellman privat nyckel

Försiktighet

Att exportera privata nycklar är en säkerhetskänslig åtgärd. Exportera endast privat nyckelmaterial när det är absolut nödvändigt och skydda det på lämpligt sätt. För nycklar som lagras i en nyckellagringsprovider (KSP) kan providern begränsa exporten baserat på nyckelprincip.

Om du vill exportera en Diffie-Hellman privat nyckel som en minnesblob anropar du BCryptExportKey med BCRYPT_DH_PRIVATE_BLOB. Den resulterande bloben innehåller ett BCRYPT_DH_KEY_BLOB sidhuvud följt av P, G, offentlig Y och privat X, var och en i big-endian byteordning.

Om du vill importera den privata nyckeln senare anropar du BCryptImportKeyPair med BCRYPT_DH_PRIVATE_BLOB.

Exempelkod

I följande exempel visas ett Diffie-Hellman nyckelutbyte mellan två parter som använder CNG. Båda parter härleder samma nyckelmaterial från den delade hemligheten och jämför de härledda byteen.

Anmärkning

Det här exemplet använder explicita Diffie-Hellman-parametrar och härleder nyckelmaterial från den delade hemligheten genom användning av BCryptDeriveKey tillsammans med BCRYPT_KDF_HASH. När du anpassar det här exemplet kontrollerar du att parameterformatet, nyckelhärledningsinställningarna och den resulterande nyckelanvändningen uppfyller programmets krav på säkerhet och samverkan.

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