EternalWindows
CNG / ハッシュオブジェクト

ある2つのデータが同一であるか調べるような場合、 ハッシュという技術を用いた検証がよく利用されます。 ハッシュとは、MD5やSHA1などのハッシュアルゴリズムを使用して、 あるデータからハッシュ値を生成することです。 この生成されたハッシュ値には、以下のような特徴があります。

・ハッシュ値の長さは、ハッシュアルゴリズムによって必ず一定サイズとなる。
・ハッシュ値から元のデータを算出することはできない。
・ハッシュ値の値は一意であり、他のデータから生成したハッシュ値と重複しない。
・1つのデータから得られるハッシュ値は常に同一となる。

このようなハッシュ値の仕組みには、様々な応用が考えられます。 たとえば、ユーザーから入力されたパスワードを検証する場合、 正しいパスワードを予めどこかに保存する必要があるように思えますが、 実際にはパスワードそのものを保存するのではなく、 パスワードのハッシュ値を保存するようにします。 そして、入力されたパスワードもハッシュし、 このハッシュ値と保存されていたハッシュ値を比較します。 ハッシュ値の性質上、同じデータから得られるハッシュ値は同一となるため、 両者が一致していた場合は、パスワードが一致していると解釈できます。 また、パスワードのハッシュ値を保存しておけば、 仮にそれが外部に漏洩しても大きな問題はありません。 ハッシュ値からは、元となるパスワードを算出することができないからです。

アルゴリズムプロバイダを使用してハッシュを行うには、 まずハッシュオブジェクトのハンドルを取得する必要があります。 これには、BCryptCreateHashを呼び出します。

NTSTATUS WINAPI BCryptCreateHash(
  BCRYPT_ALG_HANDLE hAlgorithm,
  BCRYPT_HASH_HANDLE *phHash,
  PUCHAR pbHashObject,
  ULONG cbHashObject,
  UCHAR pbSecret,
  ULONG cbSecret,
  ULONG dwFlags
);

hAlgorithmは、アルゴリズムプロバイダのハンドルを指定します。 phHashは、ハッシュオブジェクトのハンドルを受け取る変数のアドレスを指定します。 pbHashObjectは、ハッシュオブジェクトそのものを受け取るバッファを指定します。 cbHashObjectは、pbHashObjectに指定したバッファのサイズを指定します。 pbSecretは、ハッシュに使用する鍵を格納したバッファを指定します。 不要な場合は、NULLを指定することができます。 cbSecretは、pbSecretに指定したバッファのサイズを指定します。 dwFlagsは、現在は使用されないため0を指定します。

ハッシュオブジェクトのハンドルを取得すれば、それを使用してデータをハッシュすることができます。 次に示すBCryptHashDataは、指定されたデータをハッシュし、 生成したハッシュ値を管理します。

NTSTATUS WINAPI BCryptHashData(
  BCRYPT_HASH_HANDLE hHash,
  PUCHAR pbInput,
  ULONG cbInput,
  ULONG dwFlags
);

hHashは、ハッシュオブジェクトのハンドルを指定します。 pbInputは、ハッシュしたいデータを指定します。 cbInputは、pbInputのサイズを指定します。 dwFlagsは、現在は使用されないため0を指定します。

ハッシュオブジェクトが管理するハッシュ値は、BCryptFinishHashで取得することができます。

NTSTATUS WINAPI BCryptFinishHash(
  BCRYPT_HASH_HANDLE hHash,
  PUCHAR pbOutput,
  ULONG cbOutput,
  ULONG dwFlags
);

hHashは、ハッシュオブジェクトのハンドルを指定します。 pbOutputは、ハッシュ値を受け取るバッファを指定します。 cbOutputは、pbOutputに指定したバッファのサイズを指定します。 dwFlagsは、現在は使用されないため0を指定します。

不要になったハッシュオブジェクトのハンドルは、BCryptDestroyHashで破棄することになります。

NTSTATUS WINAPI BCryptDestroyHash(
  BCRYPT_HASH_HANDLE hHash
);

hHashは、破棄したいハッシュオブジェクトのハンドルを指定します。

今回のプログラムは、データをMD5アルゴリズムでハッシュし、そのハッシュ値を表示します。

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

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BCRYPT_ALG_HANDLE  hAlg;
	BCRYPT_HASH_HANDLE hHash;
	DWORD              dwResult;
	DWORD              dwHashObjectSize;
	DWORD              dwHashDataSize;
	LPBYTE             lpHashObject;
	LPBYTE             lpHashData;
	NTSTATUS           status;
	TCHAR              szData[] = TEXT("sample-data");
	TCHAR              szBuf[256];
	DWORD              dwBufferSize;

	status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, NULL, 0);
	if (!NT_SUCCESS(status))
		return 0;
	
	BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (LPBYTE)&dwHashObjectSize, sizeof(DWORD), &dwResult, 0);
	lpHashObject = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashObjectSize);
	status = BCryptCreateHash(hAlg, &hHash, lpHashObject, dwHashObjectSize, NULL, 0, 0);
	if (!NT_SUCCESS(status)) {
		HeapFree(GetProcessHeap(), 0, lpHashObject);
		BCryptCloseAlgorithmProvider(hAlg, 0);
		return 0;
	}
	
	BCryptHashData(hHash, (LPBYTE)szData, sizeof(szData), 0);

	BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (LPBYTE)&dwHashDataSize, sizeof(DWORD), &dwResult, 0);
	lpHashData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashDataSize);
	BCryptFinishHash(hHash, lpHashData, dwHashDataSize, 0);

	dwBufferSize = sizeof(szBuf);
	CryptBinaryToString(lpHashData, dwHashDataSize, CRYPT_STRING_HEX, szBuf, &dwBufferSize);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

	HeapFree(GetProcessHeap(), 0, lpHashData);
	HeapFree(GetProcessHeap(), 0, lpHashObject);
	BCryptDestroyHash(hHash);
	BCryptCloseAlgorithmProvider(hAlg, 0);

	return 0;
}

BCryptOpenAlgorithmProviderにBCRYPT_MD5_ALGORITHMを指定することで、 MD5ハッシュを行うことができるようになります。 この時点でアルゴリズムプロバイダは、ハッシュオブジェクトを扱えるように調整されていますから、 BCryptGetPropertyにBCRYPT_OBJECT_LENGTHを指定することで、 ハッシュオブジェクトのサイズを取得することができます。

BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (LPBYTE)&dwHashObjectSize, sizeof(DWORD), &dwResult, 0);
lpHashObject = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashObjectSize);
status = BCryptCreateHash(hAlg, &hHash, lpHashObject, dwHashObjectSize, NULL, 0, 0);

ハッシュオブジェクトを格納するためのメモリを確保し、 BCryptCreateHashによって実際にハッシュオブジェクトが格納されます。 オブジェクトを格納するためのメモリをアプリケーションが明示的に確保するというのは 非常に煩わしい事ですが、これは避けることのできない事実です。 BCrypt関数は、ハッシュオブジェクトをBCRYPT_HASH_HANDLEで受け取ることになりますが、 これにかこつけてlpHashObjectを開放するようなことがあってはいけません。

ハッシュオブジェクトのハンドルを取得すれば、BCryptHashDataでデータをハッシュし、 生成されたハッシュ値をBCryptFinishHashで取得することができます。

BCryptHashData(hHash, (LPBYTE)szData, sizeof(szData), 0);

BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (LPBYTE)&dwHashDataSize, sizeof(DWORD), &dwResult, 0);
lpHashData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashDataSize);
BCryptFinishHash(hHash, lpHashData, dwHashDataSize, 0);

ハッシュ値のサイズというのは、BCryptOpenAlgorithmProviderにハッシュアルゴリズムを指定した時点で決定するため、 BCryptGetPropertyにBCRYPT_HASH_LENGTHを指定することで、そのサイズを取得することができます。 また、ハッシュアルゴリズム毎にサイズが決まっているのであれば、 動的にメモリを確保する必要も特にありません。 MD5であれば16バイト、SHA1ならば20バイトのバッファを事前に定義しておけばよいことになります。

取得したハッシュ値は、CryptBinaryToStringで文字列に変換して表示するようにしています。 この関数はCryptoAPIであるため、crypt32.libへのリンクを必要とします。

dwBufferSize = sizeof(szBuf);
CryptBinaryToString(lpHashData, dwHashDataSize, CRYPT_STRING_HEX, szBuf, &dwBufferSize);
MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);dwBufferSize = sizeof(szBuf);

ここで表示される結果は、CSPの章で取り上げたハッシュオブジェクトの例と同一になります。 その節のプログラムも今回と同様にMD5アルゴリズムを利用しており、 データの値が"sample-data"となっているため特に不思議なことではありません。 CryptoAPIについての知識がある場合、BCryptの関数は比較的容易に理解することができるでしょう。 たとえば、BCryptCreateHashはCryptCreateHashに相当し、 BCryptHashDataはCryptHashData、そしてBCryptFinishHashはCryptGetHashParamとなります。


戻る