claves de Diffie-Hellman

Importante

En este artículo se usa Cryptography API: Next Generation (CNG), que es la API recomendada para nuevas aplicaciones criptográficas de Windows. Para la mayoría de las aplicaciones nuevas, considere la posibilidad de usar curva elíptica Diffie-Hellman (ECDH) con una curva con nombre estándar, como P-256 o P-384, que proporciona seguridad equivalente o más fuerte con claves más cortas y menos sobrecarga de administración de parámetros.

Las funciones cryptoAPI (CAPI1) heredadas (CryptGenKey, CryptExportKey, CryptAcquireContext, etc.) están en desuso. No los use en nuevas aplicaciones.

Generación de claves de Diffie-Hellman

Para generar un par de claves de Diffie-Hellman mediante CNG, realice los pasos siguientes:

  1. Llame a BCryptOpenAlgorithmProvider con BCRYPT_DH_ALGORITHM para obtener un identificador de proveedor de algoritmos.

  2. Llame a BCryptGenerateKeyPair para crear el par de claves, especificando el tamaño de clave en bits. Use al menos 2048 bits para una seguridad adecuada; Las claves de 512 bits (como se usan en ejemplos de CAPI1 heredados) son criptográficamente débiles y no se deben usar en el nuevo código.

  3. Establezca los parámetros DH (el primo P y el generador G) llamando a BCryptSetProperty con la propiedad BCRYPT_DH_PARAMETERS antes de llamar a BCryptFinalizeKeyPair. El valor de la propiedad debe ser una estructura BCRYPT_DH_PARAMETER_HEADER seguida inmediatamente del valor P y, a continuación, el valor G , cada cbKeyLength bytes de longitud, en orden de bytes big-endian.

    Ambas partes deben usar los mismos valores P y G . Para el nuevo código, use un grupo estandarizado conocido en lugar de generar parámetros personalizados, por ejemplo, el grupo MODP de 2048 bits 14 de RFC 3526 (que se usa en el ejemplo siguiente) proporciona un buen equilibrio de seguridad y compatibilidad. El BCRYPT_DH_PUBLIC_BLOB formato de exportación incluye P y G, por lo que un destinatario puede extraerlos del blob recibido cuando las dos partes están en equipos o procesos independientes. En un ejemplo independiente en el que ambas partes comparten el mismo proceso, el mismo blob de parámetros se puede reutilizar directamente.

  4. Llame a BCryptFinalizeKeyPair para completar la generación de claves. Se debe llamar a esta función antes de que se pueda usar o exportar la clave.

  5. Cuando la clave ya no sea necesaria, llame a BCryptDestroyKey para liberar el identificador de clave y BCryptCloseAlgorithmProvider para liberar el identificador del proveedor.

Intercambio de claves de Diffie-Hellman

El propósito del algoritmo de Diffie-Hellman es permitir que dos o más partes creen y compartan un valor secreto idéntico al compartir información a través de una red que no sea segura. La información que se comparte a través de la red es la clave pública Diffie-Hellman de cada entidad. El proceso utilizado por dos partes de intercambio de claves es el siguiente:

  • Ambas partes acuerdan en los parámetros Diffie-Hellman: un número primo (P) y un número generador (G).
  • La Parte 1 envía su clave pública de Diffie-Hellman a la Parte 2.
  • La parte 2 calcula el secreto compartido mediante su propia clave privada y la clave pública de la parte 1.
  • La Parte 2 envía su Diffie-Hellman clave pública a la Parte 1.
  • La parte 1 calcula el secreto compartido mediante su propia clave privada y la clave pública de la parte 2.
  • Ambas partes ahora tienen el mismo secreto compartido, que se puede usar para derivar una clave de cifrado simétrica.

Para preparar una clave pública de Diffie-Hellman para la transmisión:

  1. Después de generar y finalizar el par de claves, llame a BCryptExportKey utilizando BCRYPT_DH_PUBLIC_BLOB como el tipo de blob para obtener los bytes de la clave pública. El bloque incluye los valores P, G y clave pública Y, todos en orden de bytes big-endian.

  2. Transmita esos bytes a la otra parte a través de la red.

Nota:

El material clave en blobs DH de CNG (BCRYPT_DH_PUBLIC_BLOB, BCRYPT_DH_PRIVATE_BLOB) está en orden de bytes big-endian . Esto es lo contrario del formato little-endian utilizado por la obsoleta CryptoAPI (CAPI1). Tenga cuidado al interoperar con material clave codificado en CAPI1.

Para importar una clave pública Diffie-Hellman y derivar el secreto compartido:

  1. Llame a BCryptImportKeyPair con BCRYPT_DH_PUBLIC_BLOB para importar la clave pública de otra parte. Esto requiere un identificador de proveedor de algoritmos abierto con BCRYPT_DH_ALGORITHM.

  2. Llame a BCryptSecretAgreement con su propio identificador de clave privada y el identificador de clave pública importado. Esto genera un identificador de acuerdo secreto que representa el valor de secreto compartido en bruto, (Y^X) mod P.

  3. Llame a BCryptDeriveKey para derivar material de clave utilizable del secreto compartido. Use una función de derivación de claves (KDF) adecuada para su escenario; BCRYPT_KDF_HASH con SHA-256 es una opción de uso general adecuada.

  4. Use los bytes de clave derivada para construir una clave simétrica (por ejemplo, llame a BCryptGenerateSymmetricKey con BCRYPT_AES_ALGORITHM) para el cifrado o descifrado posteriores.

  5. Cuando termine, llame a BCryptDestroySecret para liberar el identificador del contrato secreto y BCryptDestroyKey para liberar todos los identificadores de clave.

Exportación de una clave privada de Diffie-Hellman

Precaución

La exportación de claves privadas es una operación sensible a la seguridad. Exporte solo material de clave privada cuando sea absolutamente necesario y protéjalo adecuadamente. En el caso de las claves almacenadas en un proveedor de almacenamiento de claves (KSP), el proveedor puede restringir la exportación en función de la directiva de claves.

Para exportar una clave privada Diffie-Hellman como un blob de memoria, llame a BCryptExportKey con BCRYPT_DH_PRIVATE_BLOB. El blob resultante contiene un encabezado BCRYPT_DH_KEY_BLOB seguido de los valoresP, G, Y público y X privado, cada uno en orden de bytes big-endian.

Para importar posteriormente la clave privada, llame a BCryptImportKeyPair con BCRYPT_DH_PRIVATE_BLOB.

Código de ejemplo

En el ejemplo siguiente se muestra un intercambio de claves Diffie-Hellman entre dos partes mediante CNG. Ambas partes derivan el mismo material clave del secreto compartido y comparan los bytes derivados.

Nota:

En este ejemplo se usan parámetros de Diffie-Hellman explícitos y se deriva material de clave del secreto compartido mediante BCryptDeriveKey con BCRYPT_KDF_HASH. Al adaptar este ejemplo, asegúrese de que el formato de parámetro, la configuración de derivación de claves y el uso de claves resultante cumplen los requisitos de seguridad e interoperabilidad de la aplicación.

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