Freigeben über


Diffie-Hellman-Schlüssel

Von Bedeutung

In diesem Artikel wird die Cryptography API: Next Generation (CNG) verwendet, die die empfohlene API für neue Windows kryptografischen Anwendungen ist. Für die meisten neuen Anwendungen sollten Sie die Verwendung von Elliptic Curve Diffie-Hellman (ECDH) mit einer Standard-benannten Kurve wie P-256 oder P-384 in Betracht ziehen, die gleichwertige oder stärkere Sicherheit mit kürzeren Schlüsseln und weniger Aufwand für die Parameterverwaltung bietet.

Die legacy CryptoAPI (CAPI1)-Funktionen (CryptGenKey, CryptExportKey, CryptAcquireContextusw.) sind veraltet. Verwenden Sie sie nicht in neuen Anwendungen.

Generieren von Diffie-Hellman Schlüsseln

Führen Sie die folgenden Schritte aus, um ein Diffie-Hellman Schlüsselpaar mit CNG zu generieren:

  1. Rufen Sie BCryptOpenAlgorithmProvider mit BCRYPT_DH_ALGORITHM auf, um ein Algorithmusanbieterhandle zu erhalten.

  2. Rufen Sie BCryptGenerateKeyPair auf, um das Schlüsselpaar zu erstellen und die Schlüsselgröße in Bits anzugeben. Verwenden Sie mindestens 2048 Bits für eine angemessene Sicherheit; 512-Bit-Schlüssel (wie in älteren CAPI1-Beispielen verwendet) sind kryptografisch schwach und dürfen nicht in neuem Code verwendet werden.

  3. Legen Sie DH-Parameter (prime P und Generator G) fest, indem Sie BCryptSetProperty mit der BCRYPT_DH_PARAMETERS Eigenschaft aufrufen, bevor Sie BCryptFinalizeKeyPair aufrufen. Der Eigenschaftswert muss eine BCRYPT_DH_PARAMETER_HEADER-Struktur sein, unmittelbar gefolgt vom P-Wert und dann dem G-Wert, jeder cbKeyLength Byte lang, in Big-Endian-Byte-Reihenfolge.

    Beide Parteien müssen dieselben P - und G-Werte verwenden. Verwenden Sie für neuen Code eine bekannte standardisierte Gruppe, anstatt benutzerdefinierte Parameter zu generieren — z. B. bietet die 2048-Bit-MODP-Gruppe 14 aus RFC 3526 (die im folgenden Beispiel verwendet wird) eine gute Balance zwischen Sicherheit und Kompatibilität. Das BCRYPT_DH_PUBLIC_BLOB Exportformat enthält P und G, sodass ein Empfänger sie aus dem empfangenen Blob extrahieren kann, wenn sich die beiden Parteien auf separaten Computern oder Prozessen befinden. In einem eigenständigen Beispiel, bei dem beide Parteien denselben Prozess gemeinsam nutzen, kann dasselbe Parameter-BLOB direkt wiederverwendet werden.

  4. Rufen Sie BCryptFinalizeKeyPair auf, um die Schlüsselgenerierung abzuschließen. Diese Funktion muss aufgerufen werden, bevor der Schlüssel verwendet oder exportiert werden kann.

  5. Wenn der Schlüssel nicht mehr benötigt wird, rufen Sie BCryptDestroyKey auf, um das Schlüsselhandle freizugeben, und BCryptCloseAlgorithmProvider , um das Anbieterhandle freizugeben.

Austauschen von Diffie-Hellman Schlüsseln

Der Zweck des Diffie-Hellman Algorithmus besteht darin, es zwei oder mehr Parteien zu ermöglichen, einen identischen, geheimen Wert zu erstellen und gemeinsam zu nutzen, indem Informationen über ein nicht sicheres Netzwerk freigegeben werden. Die Informationen, die über das Netzwerk freigegeben werden, sind der Diffie-Hellman-Öffentlichkeitsschlüssel jeder Partei. Der von zwei Schlüsselaustauschparteien verwendete Prozess lautet wie folgt:

  • Beide Parteien vereinbaren Diffie-Hellman Parameter: eine Primzahl (P) und eine Generatornummer (G).
  • Partei 1 sendet ihren öffentlichen Diffie-Hellman-Schlüssel an Partei 2.
  • Partei 2 berechnet das gemeinsame Geheimnis mithilfe seines eigenen privaten Schlüssels und des öffentlichen Schlüssels der Partei 1.
  • Partei 2 sendet ihren Diffie-Hellman öffentlichen Schlüssel an Partei 1.
  • Partei 1 berechnet das gemeinsame Geheimnis mithilfe seines eigenen privaten Schlüssels und des öffentlichen Schlüssels der Partei 2.
  • Beide Parteien verfügen jetzt über denselben gemeinsamen Geheimschlüssel, der verwendet werden kann, um einen symmetrischen Verschlüsselungsschlüssel abzuleiten.

So bereiten Sie einen Diffie-Hellman öffentlichen Schlüssel für die Übertragung vor:

  1. Rufen Sie nach dem Generieren und Abschließen des Schlüsselpaars BCryptExportKey mit BCRYPT_DH_PUBLIC_BLOB als BLOB-Typ auf, um die öffentlichen Schlüsselbytes abzurufen. Das Blob enthält die P-, G- und öffentlichen Schlüssel Y Werte, alle in der Big-End-Byte-Reihenfolge.

  2. Übertragen Sie diese Bytes über das Netzwerk an die andere Partei.

Hinweis

Schlüsselmaterial in CNG DH Blobs (BCRYPT_DH_PUBLIC_BLOB, BCRYPT_DH_PRIVATE_BLOB) ist in Big-Endian Bytereihenfolge. Dies ist das Gegenteil des Little-Endian-Formats, das von der veralteten CryptoAPI (CAPI1) verwendet wird. Achten Sie bei der Zusammenarbeit mit CAPI1-codiertem Schlüsselmaterial.

So importieren Sie einen Diffie-Hellman-öffentlichen Schlüssel und leiten Sie das gemeinsame geheime Geheimnis ab:

  1. Rufen Sie BCryptImportKeyPair mit BCRYPT_DH_PUBLIC_BLOB auf, um den öffentlichen Schlüssel der anderen Partei zu importieren. Dazu ist ein Algorithmusanbieterhandle erforderlich, das mit BCRYPT_DH_ALGORITHMgeöffnet wird.

  2. Rufen Sie BCryptSecretAgreement mit Ihrem eigenen privaten Schlüsselhandle und dem importierten öffentlichen Schlüsselhandle auf. Dies erzeugt einen geheimen Vereinbarungshandle , der den unformatierten freigegebenen geheimen Wert (Y^X) mod P darstellt.

  3. Rufen Sie BCryptDeriveKey auf, um verwendbares Schlüsselmaterial aus dem freigegebenen Geheimschlüssel abzuleiten. Verwenden Sie eine schlüsselableitungsfunktion (KDF), die für Ihr Szenario geeignet ist; BCRYPT_KDF_HASH mit SHA-256 ist eine geeignete allgemeine Wahl.

  4. Verwenden Sie die abgeleiteten Schlüsselbytes, um einen symmetrischen Schlüssel zu erstellen (z. B. indem Sie BCryptGenerateSymmetricKey mit BCRYPT_AES_ALGORITHM aufrufen) für nachfolgende Verschlüsselung oder Entschlüsselung.

  5. Rufen Sie abschließend BCryptDestroySecret auf, um den Geheimvertragshandle freizugeben, und BCryptDestroyKey , um alle Schlüsselhandles freizugeben.

Exportieren eines Diffie-Hellman privaten Schlüssels

Vorsicht

Das Exportieren privater Schlüssel ist ein sicherheitsrelevanter Vorgang. Exportieren Sie bei Bedarf nur private Schlüsselmaterial, und schützen Sie es entsprechend. Bei Schlüsseln, die in einem Schlüsselspeicheranbieter (Key Storage Provider, KSP) gespeichert sind, kann der Anbieter den Export basierend auf der Schlüsselrichtlinie einschränken.

Um einen Diffie-Hellman privaten Schlüssel als Speicherblob zu exportieren, rufen Sie BCryptExportKey mit BCRYPT_DH_PRIVATE_BLOB. Das resultierende Blob enthält einen BCRYPT_DH_KEY_BLOB Header gefolgt von den Werten P, G, public Y und private X , die jeweils in der Big-End-Byte-Reihenfolge enthalten sind.

Um den privaten Schlüssel später zu importieren, rufen Sie BCryptImportKeyPair mit BCRYPT_DH_PRIVATE_BLOB.

Beispielcode

Im folgenden Beispiel wird ein Diffie-Hellman Schlüsselaustausch zwischen zwei Parteien veranschaulicht, die CNG verwenden. Beide Parteien leiten dasselbe Schlüsselmaterial aus dem freigegebenen Geheimschlüssel ab und vergleichen die abgeleiteten Bytes.

Hinweis

In diesem Beispiel werden explizite Diffie-Hellman-Parameter verwendet, und Schlüsselmaterial wird aus dem gemeinsamen geheimen mit BCryptDeriveKeyBCRYPT_KDF_HASH abgeleitet. Stellen Sie beim Anpassen dieses Beispiels sicher, dass das Parameterformat, die Schlüsselableitungseinstellungen und die resultierende Schlüsselverwendung den Sicherheits- und Interoperabilitätsanforderungen Ihrer Anwendung entsprechen.

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