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

ハッシュとは、何らかのデータから一定サイズで一意のデータを作成する技術です。 この作成されたデータ(ハッシュ値)からは基となるデータは決して算出することができず、 基となるデータが同一である限り常に同じハッシュ値が得られます。 得られるハッシュ値は一意であり、基となるデータが異なれば得られるハッシュ値も異なります。

ある特定のデータが変更されていないかを確かめたいようなとき、 ハッシュの性質は多いに役に立ちます。 たとえば、ウィルスチェッカーのようなアプリケーションは、 各種実行可能なファイルのハッシュ値を予め保存しておき、 ファイルが実行されようとしているときにそのファイルのハッシュ値を求めます。 そして、このハッシュ値と保存しておいたハッシュ値が異なっている場合は、 両者のハッシュ値の基となるデータが同一でないということになりますから、 ファイルの中身は変更されているということになります。 ハッシュを応用した技術に署名というものがありますが、 これはデータの変更に加えてデータの作成者を保障します。

CryptoAPIで何らかのデータのハッシュ値を作成するには、まずハッシュオブジェクトを作成しなければなりません。 ハッシュオブジェクトを作成するには、CryptCreateHashを呼び出します。

BOOL WINAPI CryptCreateHash(
  HCRYPTPROV hProv, 
  ALG_ID Algid, 
  HCRYPTKEY hKey, 
  DWORD dwFlags, 
  HCRYPTHASH *phHash 
);

hProvは、CSPのハンドルを指定します。 Algidは、ハッシュを計算するアルゴリズムを示すIDを指定します。 CALG_MD5がMD5アルゴリズムを意味し、CALG_SHAがSHAアルゴリズムを意味します。 hKeyは、鍵付きハッシュアルゴリズム(CALG_HMACやCALG_MAC)を利用しない場合は、0を指定します。 dwFlagsは、将来のために予約されているため0を指定します。 phHashは、作成されたハッシュオブジェクトのハンドルが返ります。

ハッシュオブジェクトを作成したら、CryptHashDataでデータのハッシュ値を作成することができます。 この関数によって作成されたハッシュ値は、ハッシュオブジェクトによって管理されます。

BOOL WINAPI CryptHashData(
  HCRYPTHASH hHash, 
  BYTE *pbData, 
  DWORD dwDataLen, 
  DWORD dwFlags 
);

hHashは、ハッシュオブジェクトのハンドルを指定します。 pbDataは、ハッシュ値を作成したいデータを指定します。 dwDataLenは、pbDataのサイズを指定します。 dwFlagsは通常0を指定しますが、CSPによってはCRYPT_USERDATAという定数を考慮する場合があります。 この場合の考慮というのは、データの入力をUI経由でユーザーに行わせるという意味で、 そのデータをパスワード入力(主にスマートカードPIN)に見立てるための仕組みです。 これにより、カードに格納されているPINとの照合を行うことができます。 CRYPT_USERDATAを指定する場合は、dwDataLenに0を指定します。 なお、マイクロソフト社製のCSPはCRYPT_USERDATAを考慮しません。

CryptHashDataで作成されたハッシュ値は関数の引数として返るのではなく、 ハッシュオブジェクトのパラメータとして内部的に管理されるため、 CryptGetHashParamによって明示的に取得することになります。

BOOL WINAPI CryptGetHashParam(
  HCRYPTHASH hHash, 
  DWORD dwParam, 
  BYTE *pbData, 
  DWORD *pdwDataLen, 
  DWORD dwFlags 
);

hHashは、ハッシュオブジェクトのハンドルを指定します。 dwParamは、取得するパラメータを示す定数を示します。 pbDataは、パラメータを受け取るバッファのアドレスを指定します。 pdwDataLenは、パラメータのサイズを受け取る変数のアドレスを指定します。 dwFlagsは、将来のために予約されているため0を指定します。 dwParamに指定できる定数を次に示します。

定数 意味
HP_ALGID ハッシュアルゴリズムを取得する。
HP_HASHSIZE ハッシュ値のサイズを取得する。
HP_HASHVAL ハッシュ値を取得する。

ハッシュオブジェクトが不要になった場合は、CryptDestroyHashで破棄します。

BOOL WINAPI CryptDestroyHash(
  HCRYPTHASH hHash 
);

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

今回のプログラムは、ハッシュオブジェクトを使用してハッシュ値を作成する例を示しています。

#include <windows.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCRYPTPROV hProv;
	TCHAR      szData[] = TEXT("sample-data");
	DWORD      dwHashSize;
	LPBYTE     lpHashData;
	TCHAR      szBuf[256];
	DWORD      dwBufferSize;
	HCRYPTHASH hHash;
	
	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
		MessageBox(NULL, TEXT("CSPのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
		MessageBox(NULL, TEXT("ハッシュオブジェクトのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		CryptReleaseContext(hProv, 0);
		return 0;
	}
	
	CryptHashData(hHash, (LPBYTE)szData, sizeof(szData), 0);
	
	CryptGetHashParam(hHash, HP_HASHVAL, NULL, &dwHashSize, 0);
	lpHashData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashSize);
	CryptGetHashParam(hHash, HP_HASHVAL, lpHashData, &dwHashSize, 0);

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

	HeapFree(GetProcessHeap(), 0, lpHashData);

	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);

	return 0;
}

CryptCreateHashでは、MDハッシュを使用するためにCALG_MD5を指定していますが、 これが問題なく成功するのは、指定したCSPがMD5ハッシュをサポートする場合に限られることに注意してください。 今回の場合、CryptAcquireContextに指定したPROV_RSA_FULLがMD5ハッシュをサポートすることから、 選択されたCSPもMD5ハッシュをサポートするはずですが、 別のアルゴリズムIDを指定したときに関数が失敗することを踏まえ、 戻り値は確認するようにします。 CryptHashDataを呼び出せば、szDataが示すデータのハッシュ値がハッシュオブジェクトの内部に格納されるので、 作成されたハッシュ値をCryptGetHashParamで取得することができます。

CryptGetHashParam(hHash, HP_HASHVAL, NULL, &dwHashSize, 0);
lpHashData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashSize);
CryptGetHashParam(hHash, HP_HASHVAL, lpHashData, &dwHashSize, 0);

作成されたハッシュ値を取得するには、CryptGetHashParamにHP_HASHVALを指定します。 しかし、この時点ではまだ作成されたハッシュ値のサイズが分からないことから、 ハッシュ値を受け取るバッファを用意することができません。 よって、1回目の呼び出しでは、第3引数をNULLにしてサイズを受け取ることに専念し、 2回目の呼び出しで確保したサイズ分のメモリを第3引数に指定します。 なお、ハッシュサイズを取得するということであれば、 1回目のCryptGetHashParamの呼び出しにはHP_HASHSIZEを指定しても問題ないように思えますが、 このような場合、何故か2回目のHP_HASHVALを指定した呼び出しで関数が失敗することになります。

CryptoAPIでは、ハッシュのようなバイナリデータを確認する関数としてCryptBinaryToStringが用意されています。 この関数を呼び出せば、バイナリデータを16進数に変換した文字列を取得することができます。

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

第1引数と第2引数がバイナリデータとサイズとなり、第3引数が変換形式、 第4引数と第5引数が文字列を受け取るバッファとサイズになります。 ハッシュアルゴリズムや基になるデータを変更すれば、 得られるハッシュ値のバイナリデータも異なることになり、 同一の値は決して作成されていないことが分かるはずです。 CryptBinaryToStringやCryptStringToBinaryはWindowsXPから登場した関数であり、 利用するにはcrypt32.libへのリンクが必要です。

MD5関数について

ハッシュアルゴリズムというのは既に確立されているものであり、 算出されるハッシュ値はどのようなCSPを利用しても同一であることが望まれます。 これと同じように、ハッシュを計算するライブラリ等も、 既知のハッシュアルゴリズムに則った値を算出するはずですから、 CSPを介して作成したハッシュ値と何らかのライブラリを介して作成したハッシュ値は、 同一であるかの比較対象として成立するはずです。 この実験例として、crypt.dllからエクスポートされているMD5関数を利用したコードを次に示します。

#include <windows.h>

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

typedef struct {
  ULONG i[2];
  ULONG buf[4];
  unsigned char in[64];
  unsigned char digest[16];
} MD5_CTX;

typedef void (WINAPI *LPFNMD5INIT)(MD5_CTX *);
typedef void (WINAPI *LPFNMD5UPDATE)(MD5_CTX *, const unsigned char *, unsigned int);
typedef void (WINAPI *LPFNMD5FINAL)(MD5_CTX *);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HMODULE       hmod;
	LPFNMD5INIT   lpfnMD5Init;
	LPFNMD5UPDATE lpfnMD5Update;
	LPFNMD5FINAL  lpfnMD5Final;
	MD5_CTX       ctx;
	TCHAR         szData[] = TEXT("sample-data");
	TCHAR         szBuf[256];
	DWORD         dwBufferSize;

	hmod = LoadLibrary(TEXT("cryptdll.dll"));
	if (hmod == NULL)
		return 0;

	lpfnMD5Init = (LPFNMD5INIT)GetProcAddress(hmod, "MD5Init");
	lpfnMD5Update = (LPFNMD5UPDATE)GetProcAddress(hmod, "MD5Update");
	lpfnMD5Final = (LPFNMD5FINAL)GetProcAddress(hmod, "MD5Final");

	lpfnMD5Init(&ctx);
	lpfnMD5Update(&ctx, (LPBYTE)szData, sizeof(szData));
	lpfnMD5Final(&ctx);

	dwBufferSize = sizeof(szBuf);
	CryptBinaryToString(ctx.digest, sizeof(ctx.digest), CRYPT_STRING_HEX, szBuf, &dwBufferSize);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

	FreeLibrary(hmod);

	return 0;
}

MD5関数はMD5ハッシュアルゴリズムを独自に実装している関数であり、 crypt.dllからエクスポートされています。 これらの関数はヘッダーファイルには定義されていませんが、 マイクロソフトのリファレンスには必要な関数と構造体の定義が掲載されているので、 それを参考にしてコード上に直接定義を記述することになります。 必要になるのは、MD5Init、MD5Update、MD5Final、そしてMD5_CTX構造体であり、 この構造体のdigestメンバにMD5アルゴリズムで算出されたハッシュ値が格納されることになります。 実際の利用方法としては、MD5Init、MD5Update、MD5Finalを順に呼び出していくだけです。 この過程で、第1引数に指定したMD5_CTX構造体が適切に調整されていきます。 ハッシュしたいデータを指定するのはMD5Updateとなっており、 第2引数にそのハッシュしたいデータを、第3引数にはそのサイズを指定します。

上記コードの実行結果と既に紹介したCryptCreateHashのサンプルの実行結果を比べてみると、 表示されるバイナリデータ、つまりハッシュ値が同じになっていることが分かります。 これは、どちらも基となるデータが"sample-data"となっているからであり、 同じくどちらもMD5アルゴリズムを用いているからに他なりません。 また、CALG_MD5を指定したCryptCreateHashで得られるハッシュ値のサイズは常に16となりますが、 この16という値はMD5_CTX構造体のdigestメンバのサイズと等しいことも分かります。



戻る