EternalWindows
CNG / 対象鍵と暗号化

BCrypt関数で暗号化を行うためには、暗号化アルゴリズムと使用する鍵を それぞれ決定しなければなりません。 鍵には対象鍵と呼ばれる種類と非対称鍵と呼ばれる種類があり、 前者の方は暗号化と複合化に同じ鍵を使うという特徴があります。 また、対象鍵を使った暗号化を行う場合は、 BCryptOpenAlgorithmProviderに指定する暗号化アルゴリズムが、 RC4やAESなどの共通鍵暗号アルゴリズムでなければなりません。

対象鍵で暗号化したデータをアプリケーションが終了した後でも 再び利用したい場合、暗号化データをファイルなどの外部に保存することがあります。 このような場合、暗号化に使用した対象鍵も再び必要になるため、 同じく外部に保存する必要があるように思えますが、実際にはその限りではありません。 対象鍵の作成にはある一定のデータ(パスワードのような秘密の値)を指定することができ、 そのデータから作成される対象鍵は常に同一となるため、 以前に使用した対象鍵はアプリケーションの実行時に再び取得することができるのです。 対象鍵を作成するには、BCryptGenerateSymmetricKeyを呼び出します。

NTSTATUS WINAPI BCryptGenerateSymmetricKey(
  BCRYPT_ALG_HANDLE hAlgorithm,
  BCRYPT_KEY_HANDLE *phKey,
  PUCHAR pbKeyObject,
  ULONG cbKeyObject,
  PUCHAR pbSecret,
  ULONG cbSecret,
  ULONG dwFlags
);

hAlgorithmは、アルゴリズムプロバイダのハンドルを指定します。 phKeyは、鍵のハンドルを受け取る変数のアドレスを指定します。 pbKeyObjectは、鍵オブジェクトを受け取るバッファを指定します。 cbKeyObjectは、pbKeyObjecのサイズを指定します。 pbSecretは、鍵を作成するためのパスワードを指定します。 cbSecretは、pbSecretのサイズを指定します。 dwFlagsは、現在は使用されないため0を指定します。

不要になった鍵は、BCryptDestroyKeyで破棄することになります。

NTSTATUS WINAPI BCryptDestroyKey(
  BCRYPT_KEY_HANDLE hKey
);

hKeyは、破棄したい鍵のハンドルを指定します。

鍵のハンドルを取得することができれば、BCryptEncryptでデータを暗号化することができます。

NTSTATUS WINAPI BCryptEncrypt(
  BCRYPT_KEY_HANDLE hKey,
  PUCHAR pbInput,
  ULONG cbInput,
  VOID *pPaddingInfo,
  PUCHAR pbIV,
  ULONG cbIV,
  PUCHAR pbOutput,
  ULONG cbOutput,
  ULONG *pcbResult,
  ULONG dwFlags
);

hKeyは、鍵のハンドルを指定します。 pbInputは、暗号化したいデータを指定します。 cbInputは、pbInputのサイズを指定します。 pPaddingInfoは、パディング情報を格納したバッファを指定します。 対象鍵を使用する場合は、NULLになります。 pbIVは、初期化ベクトル(IV)を格納したバッファを指定します。 初期化ベクトルとは、暗号化したいデータの前部に追加するランダム値です。 このようなランダム値を含めて暗号化することにより、 作成された暗号化データ形式から元の形式を推測しにくくなります。 不要な場合は、NULLで問題ありません。 cbIVは、pbIVのサイズを指定します。 pbOutputは、暗号化されたデータを受け取るバッファを指定します。 cbOutputは、pbOutputのサイズを指定します。 pcbResultは、pbOutputにコピーしたデータのサイズを受け取る変数のアドレスを指定します。 pbOutputにNULLを指定した場合、必要なバッファのサイズが返ります。 dwFlagsは、暗号化データのサイズを調整するための定数を指定します。 不要な場合は、0を指定します。

データの複合化は、BCryptDecryptで行います。

NTSTATUS WINAPI BCryptDecrypt(
  BCRYPT_KEY_HANDLE hKey,
  PUCHAR pbInput,
  ULONG cbInput,
  VOID *pPaddingInfo,
  PUCHAR pbIV,
  ULONG cbIV,
  PUCHAR pbOutput,
  ULONG cbOutput,
  ULONG *pcbResult,
  ULONG dwFlags
);

hKeyは、鍵のハンドルを指定します。 pbInputは、複合化したいデータを指定します。 cbInputは、pbInputのサイズを指定します。 pPaddingInfoは、パディング情報を格納したバッファを指定します。 pbIVは、初期化ベクトルを格納したバッファを指定します。 これは、BCryptEncryptで指定した値と同一である必要があります。 cbIVは、pbIVのサイズを指定します。 pbOutputは、複合化されたデータを受け取るバッファを指定します。 cbOutputは、pbOutputのサイズを指定します。 pcbResultは、pbOutputにコピーしたデータのサイズを受け取る変数のアドレスを指定します。 pbOutputにNULLを指定した場合、必要なバッファのサイズが返ります。 dwFlagsは、暗号化データのサイズを調整するための定数を指定します。 これは、BCryptEncryptで指定した値と同一である必要があります。

今回のプログラムは、対象鍵による暗号化を行い、暗号化データをファイルに保存します。 また、保存された暗号化データを複合化します。

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

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

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

BOOL EncryptData(BCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize);
BOOL DecryptData(BCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BCRYPT_ALG_HANDLE hAlg;
	BCRYPT_KEY_HANDLE hKey;
	DWORD             dwKeyObjectSize;
	LPBYTE            lpKeyObject;
	LPBYTE            lpData;
	DWORD             dwDataSize;
	DWORD             dwResult;
	NTSTATUS          status;
	TCHAR             szFileName[] = TEXT("sample.dat");
	TCHAR             szData[] = TEXT("sample-data");
	TCHAR             szPassword[] = TEXT("password");
	BOOL              bEncrypt = TRUE;

	status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RC4_ALGORITHM, NULL, 0);
	if (!NT_SUCCESS(status))
		return 0;
	
	BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (LPBYTE)&dwKeyObjectSize, sizeof(DWORD), &dwResult, 0);
	lpKeyObject = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwKeyObjectSize);

	status = BCryptGenerateSymmetricKey(hAlg, &hKey, lpKeyObject, dwKeyObjectSize, (LPBYTE)szPassword, sizeof(szPassword), 0);
	if (!NT_SUCCESS(status)) {
		HeapFree(GetProcessHeap(), 0, lpKeyObject);
		BCryptCloseAlgorithmProvider(hAlg, 0);
		return 0;
	}

	if (bEncrypt) {
		EncryptData(hKey, szFileName, (LPBYTE)szData, sizeof(szData));
		MessageBox(NULL, TEXT("暗号化しました。"), TEXT("OK"), MB_OK);
	}
	else {
		DecryptData(hKey, szFileName, &lpData, &dwDataSize);
		MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
		HeapFree(GetProcessHeap(), 0, lpData);
	}

	HeapFree(GetProcessHeap(), 0, lpKeyObject);
	BCryptDestroyKey(hKey);
	BCryptCloseAlgorithmProvider(hAlg, 0);

	return 0;
}

BOOL EncryptData(BCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize)
{
	HANDLE hFile;
	DWORD  dwWriteByte;
	DWORD  dwEncryptDataSize;
	LPBYTE lpEncryptData;
	
	BCryptEncrypt(hKey, lpData, dwDataSize, NULL, NULL, 0, NULL, 0, &dwEncryptDataSize, 0);
	lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
	BCryptEncrypt(hKey, lpData, dwDataSize, NULL, NULL, 0, lpEncryptData, dwEncryptDataSize, &dwEncryptDataSize, 0);

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpEncryptData, dwEncryptDataSize, &dwWriteByte, NULL);
	CloseHandle(hFile);

	HeapFree(GetProcessHeap(), 0, lpEncryptData);
	
	return TRUE;
}

BOOL DecryptData(BCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize)
{
	HANDLE hFile;
	DWORD  dwReadByte;
	DWORD  dwEncryptDataSize;
	DWORD  dwDataSize;
	LPBYTE lpEncryptData;
	LPBYTE lpData;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	
	dwEncryptDataSize = GetFileSize(hFile, NULL);
	lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
	ReadFile(hFile, lpEncryptData, dwEncryptDataSize, &dwReadByte, NULL);
	CloseHandle(hFile);
	
	BCryptDecrypt(hKey, lpEncryptData, dwEncryptDataSize, NULL, NULL, 0, NULL, 0, &dwDataSize, 0);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	BCryptDecrypt(hKey, lpEncryptData, dwEncryptDataSize, NULL, NULL, 0, lpData, dwDataSize, &dwDataSize, 0);

	*lplpData = lpData;
	*lpdwDataSize = dwDataSize;

	HeapFree(GetProcessHeap(), 0, lpEncryptData);

	return TRUE;
}

BCryptOpenAlgorithmProviderにBCRYPT_RC4_ALGORITHMを指定することで、 RC4の暗号化を行えるようになります。 暗号化アルゴリズムを指定した場合は、アルゴリズムプロバイダが鍵オブジェクトを作成することになるため、 BCryptGetPropertyにBCRYPT_OBJECT_LENGTHを指定することで、 鍵オブジェクトのサイズを取得することができます。 このサイズ分のメモリを確保し、BCryptGenerateSymmetricKeyを呼び出します。

status = BCryptGenerateSymmetricKey(hAlg, &hKey, lpKeyObject, dwKeyObjectSize, (LPBYTE)szPassword, sizeof(szPassword), 0);

関数が成功した場合、hKeyに鍵のハンドルが返り、lpKeyObjectに鍵オブジェクトが格納されます。 鍵のハンドルは、鍵オブジェクトが存在している間で有効になるため、 ハンドルが不要になるまでオブジェクトを開放してはいけません。 パスワードは、静的に宣言した文字列を直接指定していますが、 パスワードのハッシュ値を指定することもよくあります。

bEncryptがTRUEのときに呼び出されるEncryptDataは、 データを暗号してファイルに保存します。 一方、bEncryptがFALSEのときには、DecryptDataでデータを複合化します。 どちらの場合も対象鍵の作成時には同じパスワードをしているので、 作成される鍵は同一となり、複合化に失敗するようなことはありません。 まず、EncryptDataの処理を確認します。

BCryptEncrypt(hKey, lpData, dwDataSize, NULL, NULL, 0, NULL, 0, &dwEncryptDataSize, 0);
lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
BCryptEncrypt(hKey, lpData, dwDataSize, NULL, NULL, 0, lpEncryptData, dwEncryptDataSize, &dwEncryptDataSize, 0);

1回目の呼び出しでは暗号化データを格納できるバッファのサイズが分からないので、 第7引数にNULLを指定し、第9引数で必要なサイズを取得します。 2回目の呼び出しでは、第7引数と第8引数にバッファとサイズをそれぞれ指定し、 暗号化データを取得することになります。 今回は、パディング情報を利用しないので第4引数はNULLになり、 初期化ベクトルも利用しないため第5引数と第6引数もNULLと0になります。 続いて、DecryptDataの処理を確認します。

dwEncryptDataSize = GetFileSize(hFile, NULL);
lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
ReadFile(hFile, lpEncryptData, dwEncryptDataSize, &dwReadByte, NULL);
CloseHandle(hFile);

BCryptDecrypt(hKey, lpEncryptData, dwEncryptDataSize, NULL, NULL, 0, NULL, 0, &dwDataSize, 0);
lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
BCryptDecrypt(hKey, lpEncryptData, dwEncryptDataSize, NULL, NULL, 0, lpData, dwDataSize, &dwDataSize, 0);

ファイルには暗号化データ単一を保存しているため、 GetFileSizeの戻り値が暗号化データのサイズとなります。 このサイズを基に暗号化データをファイルから取得し、 暗号化データとそのサイズをBCryptDecryptに指定します。 1回目の呼び出しでは、複合化後のデータのサイズが分からないため、 第7引数にNULLを指定し、第9引数で必要なサイズを取得します。

対象鍵のエクスポートとインポート

対象鍵はパスワードがあれば同じ鍵を作成することができますが、 状況によっては鍵そのものを保存したいこともあります。 このような場合、BCryptExportKeyを呼び出して対象鍵をエクスポートすることができます。

BCryptExportKey(hKey, NULL, BCRYPT_OPAQUE_KEY_BLOB, NULL, 0, &dwDataSize, 0);
lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
BCryptExportKey(hKey, NULL, BCRYPT_OPAQUE_KEY_BLOB, lpData, dwDataSize, &dwDataSize, 0);

対象鍵をエクスポートする場合は、第3引数にBCRYPT_OPAQUE_KEY_BLOBを指定します。 1回目の呼び出しではエクスポートされるデータのサイズを取得し、 2回目の呼び出しでエクスポートされたデータを取得します。 このデータをインポートするには、BCryptImportKeyを呼び出します。

BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (LPBYTE)&dwKeyObjectSize, sizeof(DWORD), &dwResult, 0);
lpKeyObject = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwKeyObjectSize);

BCryptImportKey(hAlg, NULL, BCRYPT_OPAQUE_KEY_BLOB, &hKey, lpKeyObject, dwKeyObjectSize, lpData, dwDataSize, 0);

対象鍵では、鍵の実体を管理するメモリが必要になるため、まずはこれを確保します。 BCryptImportKeyの呼び出しでは、BCryptExportKeyで指定したBCRYPT_OPAQUE_KEY_BLOBを指定し、 第3引数に対象鍵のハンドルが返ることになります。 また、第4引数のバッファには鍵オブジェクトが格納されます。 なお、BCryptExportKeyは対象鍵と鍵ペアのどちらもエクスポートできますが、 BCryptImportKeyは対象鍵のみしかインポートできません。 鍵ペアをインポートする場合は、BCryptImportKeyPairを呼び出します。



戻る