Important
この記事では、Cryptography API: Next Generation (CNG) を使用します。これは、新しいWindows暗号化アプリケーションに推奨される API です。 ほとんどの新しいアプリケーションでは、P-256 や P-384 などの標準の名前付き曲線で 楕円曲線 Diffie-Hellman (ECDH) を使用することを検討してください。これにより、キーが短く、パラメーター管理のオーバーヘッドが少なく、同等または強力なセキュリティが提供されます。
従来の CryptoAPI (CAPI1) 関数 (CryptGenKey、 CryptExportKey、 CryptAcquireContextなど) は非推奨です。 新しいアプリケーションでは使用しないでください。
- Diffie-Hellman キーの生成
- Diffie-Hellman キー交換
- Diffie-Hellman 秘密キー のエクスポート
- コード例
- 関連コンテンツ
Diffie-Hellman キーの生成
CNG を使用して Diffie-Hellman キー ペアを生成するには、次の手順に従います。
を使用して
BCRYPT_DH_ALGORITHMを呼び出して、アルゴリズム プロバイダー ハンドルを取得します。BCryptGenerateKeyPair を呼び出してキー ペアを作成し、キー サイズをビット単位で指定します。 適切なセキュリティを確保するために、少なくとも 2048 ビットを使用します。(従来の CAPI1 の例で使用されている) 512 ビット キーは暗号的に脆弱であり、新しいコードでは使用できません。
BCryptFinalizeKeyPair を呼び出す前に、 プロパティで
BCRYPT_DH_PARAMETERS呼び出して DH パラメーター (素 P およびジェネレーター G) を設定します。 プロパティ値は 、BCRYPT_DH_PARAMETER_HEADER 構造体の直後に P 値が続き、 次に G 値 (各cbKeyLengthバイトの長さ)がビッグ エンディアン バイト順に続く必要があります。両当事者は、同じ P 値と G 値を使用する必要があります。 新しいコードでは、カスタム パラメーターを生成するのではなく、よく知られた標準化されたグループを使用します。たとえば、 RFC 3526 の 2048 ビット MODP グループ 14 (以下の例で使用) は、セキュリティと互換性のバランスが良くなります。
BCRYPT_DH_PUBLIC_BLOBエクスポート形式には P と G が含まれているため、受信者は、2 人の当事者が別々のマシンまたはプロセス上にいるときに、受信した BLOB からそれらを抽出できます。 両方の当事者が同じプロセスを共有する自己完結型の例では、同じパラメーター BLOB を直接再利用できます。BCryptFinalizeKeyPair を呼び出して、キーの生成を完了します。 この関数は、キーを使用またはエクスポートする前に呼び出す必要があります。
キーが不要になったら、 BCryptDestroyKey を 呼び出してキー ハンドルを解放し、 BCryptCloseAlgorithmProvider を呼び出してプロバイダー ハンドルを解放します。
Diffie-Hellman キーの交換
Diffie-Hellman アルゴリズムの目的は、セキュリティで保護されていないネットワーク経由で情報を共有することで、2 人以上の関係者が同じシークレット値を作成して共有できるようにすることです。 ネットワーク経由で共有される情報は、各パーティの Diffie-Hellman 公開キーです。 2 つのキー交換パーティによって使用されるプロセスは次のとおりです。
- 両当事者は、素数(P)と発電機番号(G)の Diffie-Hellman パラメータに同意する。
- パーティー 1 は、その Diffie-Hellman 公開キーを Party 2 に送信します。
- Party 2 は、独自の秘密キーと Party 1 の公開キーを使用して共有シークレットを計算します。
- パーティー 2 は、その Diffie-Hellman 公開キーを Party 1 に送信します。
- Party 1 は、独自の秘密キーと Party 2 の公開キーを使用して共有シークレットを計算します。
- これで、両方の当事者が同じ共有シークレットを持つようになり、これを使用して対称暗号化キーを派生させることができます。
転送用の Diffie-Hellman 公開キーを準備するには:
キー ペアを生成して終了したら、BLOB の種類として を使用して
BCRYPT_DH_PUBLIC_BLOBを呼び出して、公開キーバイトを取得します。 BLOB には P、G、公開キーの Y 値がすべてビッグ エンディアンバイト順に含まれます。これらのバイトをネットワーク経由で相手に送信します。
注
CNG DH BLOB (BCRYPT_DH_PUBLIC_BLOB、 BCRYPT_DH_PRIVATE_BLOB) のキー マテリアルは、 ビッグ エンディアン バイト順です。 これは、非推奨の CryptoAPI (CAPI1) で使用されるリトル エンディアン形式とは逆です。 CAPI1 でエンコードされたキー マテリアルと相互運用するときは注意してください。
Diffie-Hellman 公開キーをインポートし、共有シークレットを派生するには:
で
BCRYPT_DH_PUBLIC_BLOBを呼び出して、相手の公開キーをインポートします。 これには、BCRYPT_DH_ALGORITHMで開かれたアルゴリズム プロバイダー ハンドルが必要です。独自の秘密キー ハンドルとインポートされた公開キー ハンドルを使用して BCryptSecretAgreement を呼び出します。 これにより、未加工の共有シークレット値 (Y^X) mod P を表す 秘密契約 ハンドルが生成されます。
BCryptDeriveKey を呼び出して、共有シークレットから使用可能なキー マテリアルを派生させます。 シナリオに適したキー派生関数 (KDF) を使用します。
BCRYPT_KDF_HASHを使用したSHA-256は、適切な汎用の選択肢です。派生キー バイトを使用して、以降の暗号化または復号化のために対称キー (たとえば、 で
BCRYPT_AES_ALGORITHMを呼び出す) を構築します。完了したら、 BCryptDestroySecret を呼び出して秘密契約ハンドルを解放し、 BCryptDestroyKey を 呼び出してすべてのキー ハンドルを解放します。
Diffie-Hellman 秘密キーのエクスポート
注意事項
秘密キーのエクスポートは、セキュリティに依存する操作です。 必要な場合にのみ秘密キーマテリアルをエクスポートし、適切に保護します。 キー ストレージ プロバイダー (KSP) に格納されているキーの場合、プロバイダーはキー ポリシーに基づいてエクスポートを制限できます。
Diffie-Hellman 秘密キーをメモリ BLOB としてエクスポートするには、で BCRYPT_DH_PRIVATE_BLOB呼び出します。 結果の BLOB には 、BCRYPT_DH_KEY_BLOB ヘッダーの後に P、 G、パブリック Y、プライベート X の値が続き、それぞれビッグ エンディアン バイト順に格納されます。
後で秘密キーをインポートするには、で BCRYPT_DH_PRIVATE_BLOB を呼び出します。
コード例
次の例は、CNG を使用した 2 つのパーティ間の Diffie-Hellman キー交換を示しています。 両方の当事者が共有シークレットから同じキー マテリアルを派生させ、派生バイトを比較します。
注
この例では、明示的な Diffie-Hellman パラメーターを使用し、BCryptDeriveKeyでBCRYPT_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;
}
関連するコンテンツ
- Cryptography API: Next Generation (CNG)
- 一般的な CNG プログラミングの概要
- BCryptOpenAlgorithmProvider
- BCryptGenerateKeyPair
- BCryptFinalizeKeyPair
- BCryptSecretAgreement
- BCryptDeriveKey
- BCRYPT_DH_KEY_BLOB
- BCRYPT_DH_PARAMETER_HEADER
- CNG アルゴリズム識別子