Delen via


Diffie-Hellman sleutels

Belangrijk

In dit artikel wordt de Cryptography-API: Next Generation (CNG) gebruikt. Dit is de aanbevolen API voor nieuwe Windows cryptografische toepassingen. Voor de meeste nieuwe toepassingen kunt u overwegen Elliptic Curve Diffie-Hellman (ECDH) te gebruiken met een standaard benoemde curve, zoals P-256 of P-384, die gelijkwaardige of sterkere beveiliging biedt met kortere sleutels en minder overhead voor parameterbeheer.

De verouderde CryptoAPI-functies (CAPI1) (CryptGenKey, CryptExportKeyenzovoort CryptAcquireContext) zijn afgeschaft. Gebruik ze niet in nieuwe toepassingen.

Diffie-Hellman sleutels genereren

Voer de volgende stappen uit om een Diffie-Hellman sleutelpaar te genereren met behulp van CNG:

  1. Roep BCryptOpenAlgorithmProvider aan met BCRYPT_DH_ALGORITHM om een algoritmeprovider-handle te verkrijgen.

  2. Roep BCryptGenerateKeyPair aan om het sleutelpaar te maken, waarbij de sleutelgrootte in bits wordt opgegeven. Gebruik ten minste 2048 bits voor voldoende beveiliging; 512-bits sleutels (zoals gebruikt in verouderde CAPI1-voorbeelden) zijn cryptografisch zwak en mogen niet worden gebruikt in nieuwe code.

  3. Stel DH-parameters (de prime P en generator G) in door BCryptSetProperty aan te roepen met de BCRYPT_DH_PARAMETERS eigenschap voordat u BCryptFinalizeKeyPair aanroept. De eigenschapswaarde moet een BCRYPT_DH_PARAMETER_HEADER structuur zijn, gevolgd door de P-waarde en vervolgens de G-waarde, elke cbKeyLength bytes in lengte, in big-endian byte-volgorde.

    Beide partijen moeten dezelfde P - en G-waarden gebruiken. Gebruik voor nieuwe code een bekende gestandaardiseerde groep in plaats van aangepaste parameters te genereren, bijvoorbeeld de 2048-bits MODP-groep 14 van RFC 3526 (gebruikt in het onderstaande voorbeeld) biedt een goede balans tussen beveiliging en compatibiliteit. De BCRYPT_DH_PUBLIC_BLOB exportindeling bevat P en G, zodat een ontvanger deze kan extraheren uit de ontvangen blob wanneer de twee partijen zich op afzonderlijke machines of processen bevinden. In een zelfstandig voorbeeld waarbij beide partijen hetzelfde proces delen, kan dezelfde parameter-blob rechtstreeks opnieuw worden gebruikt.

  4. Roep BCryptFinalizeKeyPair aan om de sleutelgeneratie te voltooien. Deze functie moet worden aangeroepen voordat de sleutel kan worden gebruikt of geëxporteerd.

  5. Wanneer de sleutel niet meer nodig is, roept u BCryptDestroyKey aan om de sleutelgreep vrij te geven en BCryptCloseAlgorithmProvider om de providerhandgreep vrij te geven.

Diffie-Hellman sleutels uitwisselen

Het doel van het Diffie-Hellman algoritme is het mogelijk te maken voor twee of meer partijen om een identieke geheime waarde te maken en te delen door informatie te delen via een netwerk dat niet veilig is. De informatie die via het netwerk wordt gedeeld, is de Diffie-Hellman openbare sleutel van elke partij. Het proces dat door twee sleuteluitwisselingspartijen wordt gebruikt, is als volgt:

  • Beide partijen zijn het eens met Diffie-Hellman parameters: een priemgetal (P) en een generatornummer (G).
  • Partij 1 stuurt zijn Diffie-Hellman openbare sleutel naar partij 2.
  • Party 2 berekent het gedeelde geheim met behulp van een eigen persoonlijke sleutel en de openbare sleutel van party 1.
  • Partij 2 stuurt zijn Diffie-Hellman openbare sleutel naar partij 1.
  • Party 1 berekent het gedeelde geheim met behulp van een eigen persoonlijke sleutel en de openbare sleutel van Party 2.
  • Beide partijen hebben nu hetzelfde gedeelde geheim, dat kan worden gebruikt om een symmetrische versleutelingssleutel af te leiden.

Een Diffie-Hellman openbare sleutel voorbereiden voor verzending:

  1. Nadat u het sleutelpaar hebt gegenereerd en voltooid, roept u BCryptExportKey aan als BCRYPT_DH_PUBLIC_BLOB blobtype om de bytes van de openbare sleutel op te halen. De blob bevat de waarden P, G en openbare sleutel Y , allemaal in bytevolgorde big-endian.

  2. Verzend deze bytes via het netwerk naar de andere partij.

Opmerking

Sleutelmateriaal in CNG DH-blobs (BCRYPT_DH_PUBLIC_BLOB, BCRYPT_DH_PRIVATE_BLOB) bevindt zich in big-endian bytevolgorde . Dit is het tegenovergestelde van de little-endian-indeling die wordt gebruikt door de afgeschafte CryptoAPI (CAPI1). Wees voorzichtig bij het interopereren met CAPI1-gecodeerd sleutelmateriaal.

Een Diffie-Hellman openbare sleutel importeren en het gedeelde geheim afleiden:

  1. Roep BCryptImportKeyPair aan om BCRYPT_DH_PUBLIC_BLOB de openbare sleutel van de andere partij te importeren. Hiervoor moet een algoritmeprovider worden geopend met BCRYPT_DH_ALGORITHM.

  2. Roep BCryptSecretAgreement aan met uw eigen privésleutelhandvat en het geïmporteerde openbare sleutelhandvat. Hiermee wordt een geheime overeenkomst verwerkt die de onbewerkte gedeelde geheime waarde (Y^X) mod P vertegenwoordigt.

  3. Roep BCryptDeriveKey aan om bruikbaar sleutelmateriaal af te leiden van het gedeelde geheim. Gebruik een sleutel derivation-functie (KDF) die geschikt is voor uw scenario; BCRYPT_KDF_HASH met SHA-256 is een geschikte keuze voor algemeen gebruik.

  4. Gebruik de afgeleide sleutelbytes om een symmetrische sleutel te maken (bijvoorbeeld BCryptGenerateSymmetricKey aanroepen met BCRYPT_AES_ALGORITHM) voor volgende versleuteling of ontsleuteling.

  5. Wanneer u klaar bent, roept u BCryptDestroySecret aan om de handle van de geheime overeenkomst vrij te geven en BCryptDestroyKey om alle sleutelhandles vrij te geven.

Een Diffie-Hellman persoonlijke sleutel exporteren

Waarschuwing

Het exporteren van persoonlijke sleutels is een beveiligingsgevoelige bewerking. Exporteer alleen persoonlijk sleutelmateriaal wanneer dit absoluut noodzakelijk is en beveilig het op de juiste wijze. Voor sleutels die zijn opgeslagen in een sleutelopslagprovider (KSP), kan de provider de export beperken op basis van sleutelbeleid.

Als u een Diffie-Hellman persoonlijke sleutel als een geheugen-blob wilt exporteren, roept u BCryptExportKey aan met BCRYPT_DH_PRIVATE_BLOB. De resulterende blob bevat een BCRYPT_DH_KEY_BLOB header gevolgd door de P-, G-, openbare Y- en privé-X-waarden , elk in bytevolgorde big-endian.

Als u de persoonlijke sleutel later wilt importeren, roept u BCryptImportKeyPair aan met BCRYPT_DH_PRIVATE_BLOB.

Voorbeeldcode

In het volgende voorbeeld ziet u een Diffie-Hellman sleuteluitwisseling tussen twee partijen met behulp van CNG. Beide partijen leiden hetzelfde sleutelmateriaal af van het gedeelde geheim en vergelijken de afgeleide bytes.

Opmerking

In dit voorbeeld worden expliciete Diffie-Hellman parameters gebruikt en wordt sleutelmateriaal afgeleid van het gedeelde geheim door BCryptDeriveKey met behulp van BCRYPT_KDF_HASH. Wanneer u dit voorbeeld aanpast, moet u ervoor zorgen dat de parameterindeling, sleutel-afleidingsinstellingen en het resulterende sleutelgebruik voldoen aan de beveiligings- en interoperabiliteitsvereisten van uw toepassing.

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