중요합니다
이 문서에서는 새 Windows 암호화 애플리케이션에 권장되는 API인 CNG(차세대) 를 사용합니다. 대부분의 새 애플리케이션의 경우 P-256 또는 P-384와 같은 표준 명명된 곡선과 함께 ECDH(타원 곡선 Diffie-Hellman) 를 사용하는 것이 좋습니다. 이 곡선은 키가 짧고 매개 변수 관리 오버헤드가 적어 동일하거나 더 강력한 보안을 제공합니다.
레거시 CAPI1(CryptoAPI) 함수(CryptGenKey, CryptExportKey등 CryptAcquireContext)는 더 이상 사용되지 않습니다. 새 애플리케이션에서는 사용하지 마세요.
Diffie-Hellman 키 생성
CNG를 사용하여 Diffie-Hellman 키 쌍을 생성하려면 다음 단계를 수행합니다.
BCryptOpenAlgorithmProvider를
BCRYPT_DH_ALGORITHM호출하여 알고리즘 공급자 핸들을 가져옵니다.BCryptGenerateKeyPair를 호출하여 키 쌍을 만들고 키 크기를 비트 단위로 지정합니다. 적절한 보안을 위해 2048비트 이상을 사용합니다. 512비트 키(레거시 CAPI1 예제에 사용됨)는 암호화적으로 약하며 새 코드에서 사용해서는 안 됩니다.
DH 매개 변수(프라임 P 및 생성기 G)를 설정하기 위해 먼저
BCRYPT_DH_PARAMETERS속성을 사용하여 BCryptSetProperty를 호출한 후 BCryptFinalizeKeyPair을 호출합니다. 속성 값은 BCRYPT_DH_PARAMETER_HEADER 구조체여야 하며, 그 다음에 P 값과 G 값이 각각cbKeyLength바이트 길이로 big-endian 바이트 순서로 와야 합니다.두 당사자는 동일한 P 및 G 값을 사용해야 합니다. 새 코드의 경우 사용자 지정 매개 변수를 생성하는 대신 잘 알려진 표준화된 그룹을 사용합니다. 예를 들어 RFC 3526 의 2048비트 MODP 그룹 14(아래 예제에 사용됨)는 보안 및 호환성의 균형을 잘 제공합니다. 내보내기 형식에는
BCRYPT_DH_PUBLIC_BLOBP 및 G가 포함되므로 두 당사자가 별도의 컴퓨터 또는 프로세스에 있을 때 받는 사람이 수신된 Blob에서 추출할 수 있습니다. 두 당사자가 동일한 프로세스를 공유하는 자체 포함 예제에서 동일한 매개 변수 Blob을 직접 다시 사용할 수 있습니다.BCryptFinalizeKeyPair를 호출하여 키 생성을 완료합니다. 키를 사용하거나 내보내려면 먼저 이 함수를 호출해야 합니다.
키가 더 이상 필요하지 않으면 BCryptDestroyKey 를 호출하여 키 핸들을 해제하고 BCryptCloseAlgorithmProvider 를 호출하여 공급자 핸들을 해제합니다.
Diffie-Hellman 키 교환
Diffie-Hellman 알고리즘의 목적은 둘 이상의 당사자가 안전하지 않은 네트워크를 통해 정보를 공유하여 동일한 비밀 값을 만들고 공유할 수 있도록 하는 것입니다. 네트워크를 통해 공유되는 정보는 각 당사자의 Diffie-Hellman 공개 키입니다. 두 키 교환 당사자가 사용하는 프로세스는 다음과 같습니다.
- 양 당사자는 소수(P) 및 생성기 번호(G)와 같은 Diffie-Hellman 매개 변수에 동의합니다.
- 파티 1은 파티 2에 Diffie-Hellman 공개 키를 보냅니다.
- 파티 2는 자체 프라이빗 키와 파티 1의 공개 키를 사용하여 공유 비밀을 계산합니다.
- 파티 2는 Diffie-Hellman 공개 키를 파티 1에 보냅니다.
- 파티 1은 자체 프라이빗 키와 파티 2의 공개 키를 사용하여 공유 비밀을 계산합니다.
- 이제 두 당사자는 대칭 암호화 키를 파생하는 데 사용할 수 있는 동일한 공유 비밀을 갖게 됩니다.
전송을 위해 Diffie-Hellman 공개 키를 준비하려면 다음을 수행합니다.
키 쌍을 생성하고 완료한 후 Blob 형식으로
BCRYPT_DH_PUBLIC_BLOB를 호출하여 공개 키 바이트를 가져옵니다. Blob에는 P, G 및 공개 키 Y 값이 모두 big-endian 바이트 순서로 포함됩니다.네트워크를 통해 해당 바이트를 상대방에게 전송합니다.
메모
CNG DH Blob(BCRYPT_DH_PUBLIC_BLOB, BCRYPT_DH_PRIVATE_BLOB)의 키 자료는 big-endian 바이트 순서입니다. 이는 사용되지 않는 CAPI1(CryptoAPI)에서 사용하는 little-endian 형식과 반대입니다. CAPI1로 인코딩된 키 자료와 상호 운용할 때는 주의해야 합니다.
Diffie-Hellman 공개 키를 가져오고 공유 비밀을 파생하려면 다음을 수행합니다.
BCryptImportKeyPair를
BCRYPT_DH_PUBLIC_BLOB호출하여 상대방의 공개 키를 가져옵니다. 이렇게 하려면 알고리즘 공급자 핸들이BCRYPT_DH_ALGORITHM으로 열려 있어야 합니다.사용자 고유의 프라이빗 키 핸들 및 가져온 공개 키 핸들을 사용하여 BCryptSecretAgreement 를 호출합니다. 공유된 원시 비밀 값 (Y^X) mod P를 나타내는 비밀 협정 핸들이 생성됩니다.
BCryptDeriveKey를 호출하여 공유 비밀에서 사용 가능한 키 자료를 파생합니다. 시나리오에 적합한 KDF(키 파생 함수)를 사용합니다.
BCRYPT_KDF_HASHSHA-256은 적합한 범용 선택입니다.파생 키 바이트를 사용하여 후속 암호화 또는 암호 해독을 위해 대칭 키(예: BCryptGenerateSymmetricKey 호출
BCRYPT_AES_ALGORITHM)를 생성합니다.완료되면 BCryptDestroySecret 을 호출하여 비밀 계약 핸들을 해제하고 BCryptDestroyKey 를 호출하여 모든 키 핸들을 해제합니다.
Diffie-Hellman 프라이빗 키 내보내기
주의
프라이빗 키를 내보내는 것은 보안에 중요한 작업입니다. 반드시 필요한 경우에만 프라이빗 키 자료를 내보내고 적절하게 보호합니다. KSP(키 스토리지 공급자)에 저장된 키의 경우 공급자는 키 정책에 따라 내보내기를 제한할 수 있습니다.
Diffie-Hellman 프라이빗 키를 메모리 Blob으로 내보내려면 BCryptExportKey를 BCRYPT_DH_PRIVATE_BLOB 호출하십시오. 결과 Blob에는 BCRYPT_DH_KEY_BLOB 헤더와 P, G, public Y 및 private X 값이 각각 big-endian 바이트 순서로 포함됩니다.
나중에 프라이빗 키를 가져오려면 .를 사용하여 BCryptImportKeyPair 를 BCRYPT_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;
}
관련 콘텐츠
- 암호화 API: 차세대(CNG)
- 일반적인 CNG 프로그래밍 개요
- BCryptOpenAlgorithmProvider
- BCryptGenerateKeyPair
- BCryptFinalizeKeyPair
- BCryptSecretAgreement
- BCryptDeriveKey
- BCRYPT_DH_KEY_BLOB
- BCRYPT_DH_PARAMETER_HEADER
- CNG 알고리즘 식별자