EternalWindows
CNG / 鍵ペアの作成

アルゴリズムプロバイダが扱える鍵には、前節で述べた対象鍵以外に非対称鍵というものがあります。 これは、秘密鍵と公開鍵という2種類の鍵を持った鍵ペアであり、 片方の鍵で暗号化したデータは、もう片方の鍵でしか複合化できないという特徴があります。 よく取り上げられる例としては、片方の鍵(公開鍵)を通信相手に送信し、 その通信相手に送信した鍵を使って暗号化を行わすというものがあります。 このようにすれば、データを複合化できるのはもう片方の鍵(秘密鍵)を持つアプリケーションだけとなり、 安全性が高まります。 ただし、実際には公開鍵や秘密鍵そのものでデータの暗号化を行うことは少なく、 多くの場合は対称鍵の交換や署名などに利用します。

鍵ペアを作成するためには、BCryptOpenAlgorithmProviderにRSAなどの公開鍵暗号 アルゴリズムやECDSAなどの署名アルゴリズム、またはECDHなどの秘密協定アルゴリズムを 指定している必要があります。 このような場合、BCryptGenerateKeyPairで鍵ペアを作成することができます。

NTSTATUS WINAPI BCryptGenerateKeyPair(
  BCRYPT_ALG_HANDLE hAlgorithm,
  BCRYPT_KEY_HANDLE *phKey,
  ULONG dwLength,
  ULONG dwFlags
);

hAlgorithmは、アルゴリズムプロバイダのハンドルを指定します。 phKeyは、鍵のハンドルを受け取る変数のアドレスを指定します。 dwLengthは、鍵の長さを指定します。 これは、BCryptOpenAlgorithmProviderに指定したアルゴリズムによって変化します。 dwFlagsは、現在は使用されないため0を指定します。

BCryptGenerateKeyPairで作成した鍵ペアを実際に使用するためには、 BCryptFinalizeKeyPairで鍵ペアを完成させておく必要があります。 鍵ペアに対してプロパティなどの変更が必要な場合は、 このBCryptFinalizeKeyPairの呼び出しの前に行っておく必要があります。

NTSTATUS WINAPI BCryptFinalizeKeyPair(
  BCRYPT_KEY_HANDLE hKey,
  ULONG dwFlags
);

hKeyは、BCryptGenerateKeyPairで作成した鍵ペアのハンドルを指定します。 dwFlagsは、現在は使用されないため0を指定します。

鍵ペアは常にランダムな値で作成されるため、 アプリケーションの終了後でも再び同じ鍵を利用したい場合は、 ファイルなどの外部に保存する必要があります。 CryptoAPIでは、CSPが鍵コンテナという抽象化された場所で、 アプリケーションから透過的に鍵を管理していましたが、 CNGのアルゴリズムプロバイダはこのような鍵の管理を行うことはありません。 つまり、アプリケーションが明示的に鍵をエクスポートする必要があります。 鍵をエクスポートするには、BCryptExportKeyを呼び出します。

NTSTATUS WINAPI BCryptExportKey(
  BCRYPT_KEY_HANDLE hKey,
  BCRYPT_KEY_HANDLE hExportKey,
  LPCWSTR pszBlobType,
  PUCHAR pbOutput,
  LONG cbOutput,
  ULONG *pcbResult,
  ULONG dwFlags
);

hKeyは、エクスポートしたい鍵のハンドルを指定します。 hExportKeyは、使用されないため0を指定します。 pszBlobTypeは、エクスポートの形式を表す定数を指定します。 pbOutputは、エクスポートされたデータを受け取るバッファを指定します。 cbOutputは、pbOutputのサイズを指定します。 pcbResultは、pbOutputにコピーされたデータのサイズを受け取る変数のアドレスを指定します。 pbOutputがNULLの場合は、必要なバッファのサイズを指定します。 dwFlagsは、現在は使用されないため0を指定します。

今回のプログラムは、次節の署名アプリケーションが使うための鍵ペアを作成し、 それをファイルに保存します。

#include <windows.h>
#include <bcrypt.h>

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)Status) >= 0)
#endif

#pragma comment (lib, "bcrypt.lib")

BOOL ExportKeyData(BCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPWSTR lpszType);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BCRYPT_ALG_HANDLE hAlg;
	BCRYPT_KEY_HANDLE hKey;
	NTSTATUS          status;
	TCHAR             szPrivKeyFileName[] = TEXT("privkey.dat");
	TCHAR             szPubKeyFileName[] = TEXT("pubkey.dat");

	status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_ECDSA_P256_ALGORITHM, NULL, 0);
	if (!NT_SUCCESS(status))
		return 0;
	
	status = BCryptGenerateKeyPair(hAlg, &hKey, 256, 0);
	if (!NT_SUCCESS(status)) {
		BCryptCloseAlgorithmProvider(hAlg, 0);
		return 0;
	}

	BCryptFinalizeKeyPair(hKey, 0);
	
	ExportKeyData(hKey, szPrivKeyFileName, BCRYPT_ECCPRIVATE_BLOB);
	ExportKeyData(hKey, szPubKeyFileName, BCRYPT_ECCPUBLIC_BLOB);

	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	BCryptDestroyKey(hKey);
	BCryptCloseAlgorithmProvider(hAlg, 0);

	return 0;
}

BOOL ExportKeyData(BCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPWSTR lpszType)
{
	HANDLE   hFile;
	DWORD    dwWriteByte;
	DWORD    dwDataSize;
	LPBYTE   lpData;
	NTSTATUS status;

	BCryptExportKey(hKey, NULL, lpszType, NULL, 0, &dwDataSize, 0);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	status = BCryptExportKey(hKey, NULL, lpszType, lpData, dwDataSize, &dwDataSize, 0);
	if (!NT_SUCCESS(status)) {
		HeapFree(GetProcessHeap(), 0, lpData);
		return FALSE;
	}
	
	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpData, dwDataSize, &dwWriteByte, NULL);
	CloseHandle(hFile);

	HeapFree(GetProcessHeap(), 0, lpData);

	return TRUE;
}

BCryptOpenAlgorithmProviderに指定しているBCRYPT_ECDSA_P256_ALGORITHMは、 署名アルゴリズムの1つです。 このアルゴリズムを使用する場合の鍵のサイズは256ビットになるため、 BCryptGenerateKeyPairの第3引数に256を指定します。 BCryptFinalizeKeyPairよって鍵ペアは完成され、 鍵のハンドルを必要とする関数を呼び出すことができるようになります。

ExportKeyData(hKey, szPrivKeyFileName, BCRYPT_ECCPRIVATE_BLOB);
ExportKeyData(hKey, szPubKeyFileName, BCRYPT_ECCPUBLIC_BLOB);

ExportKeyDataという自作関数は、第1引数に指定された鍵ペアを、 第3引数の形式でエクスポートします。 また、そのエクスポートしたデータを第2引数のファイルに保存します。 1つ目の呼び出しに指定しているBCRYPT_ECCPRIVATE_BLOBは、 ECDHやECDSAなどの楕円曲線アルゴリズムの秘密鍵をエクスポートし、 BCRYPT_ECCPUBLIC_BLOBは楕円曲線アルゴリズムの公開鍵をエクスポートします。 ExportKeyDataの内部を次に示します。

BCryptExportKey(hKey, NULL, lpszType, NULL, 0, &dwDataSize, 0);
lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
status = BCryptExportKey(hKey, NULL, lpszType, lpData, dwDataSize, &dwDataSize, 0);
if (!NT_SUCCESS(status)) {
	HeapFree(GetProcessHeap(), 0, lpData);
	return FALSE;
}

1回目の呼び出しではエクスポートされるデータのサイズが分からないため 第4引数にNULLを指定し、第6引数で必要なサイズを受け取ります。 そして、バッファを確保してから2回目の呼び出しで、 実際にエクスポートされたデータを取得します。 BCryptExportKeyでは、鍵のアルゴリズムに対してエクスポート形式が正しくない場合は 関数が失敗するため、戻り値の確認は特に重要です。


戻る