EternalWindows
CSP / 鍵コンテナ

CSPが実装するアルゴリズムを利用するためには、 まずCSPのハンドルを取得しなければなりません。 これには、CryptAcquireContextを呼び出します。

BOOL WINAPI CryptAcquireContext(
  HCRYPTPROV *phProv,
  LPCTSTR pszContainer,
  LPCTSTR pszProvider,
  DWORD dwProvType,
  DWORD dwFlags
);

phProvは、HCRYPTPROV型の変数のアドレスを指定します。 関数が成功するとこの変数にCSPのハンドルが格納されます。 pszContainerは、開きたい鍵コンテナの名前を指定します。 pszProviderは、ハンドルを取得したいCSPの名前を指定します。 dwProvTypeは、pszProviderに指定したCSPのプロバイダタイプを指定します。 dwFlagsは、鍵コンテナに関する定数を指定します。 CSPのハンドルを含めて、CryptoAPIで用いられる一部のハンドルは、 その実体がDWORD型となっているため、 ハンドルがNULLかどうかのチェックを行ってはいけません。 関数が成功しているかどうかは、戻り値がTRUEかどうかで判断するようにします。

鍵コンテナというのは、アプリケーションから見た鍵の格納場所のことです。 鍵は暗号化の際に用いる一定サイズの数値であり、 CSPはこの鍵をファイルやレジストリ、さらにはスマートカードなど、 何らかの媒体に保存することができます。 この実際の格納場所を抽象化した表現が鍵コンテナであり、 実際の格納場所に代わって鍵コンテナに名前を設けることにより、 名前を指定すれば鍵を取得できるという簡略化をもたらすことができます。 また、鍵の取得に必ずCSPを介入させるようにすれば、 鍵が不正に利用されることもなくなります。 鍵コンテナに格納される鍵は厳密には鍵ペアと呼ばれますが、 これについては後の節で説明します。

次に、CryptAcquireContextのdwFlagsに指定できる値を示します。

意味
0 pszContainerに指定された名前の鍵コンテナをオープンする。 NULLを指定した場合は、デフォルトの名前を持った鍵コンテナをオープンする。 デフォルトの名前というのはCSPによって異なるが、 現在ログオンしているユーザー名とされることが多い。 返されたCSPのハンドルで参照されるのは、オープンした鍵コンテナだけである。
CRYPT_NEWKEYSET pszContainerに指定した名前を持った鍵コンテナを作成する。 NULLを指定した場合は、デフォルトの名前を持った鍵コンテナを作成する。
CRYPT_MACHINE_KEYSET CSPは通常の鍵コンテナとマシンコンテナという2種類の鍵コンテナを作成することができ、 通常の鍵コンテナはコードを実行しているユーザーのプロファイルに保存されることが多い。 このため、そのユーザーがログオンしていない場合は誰も鍵コンテナを参照することができない。 一方、マシンコンテナは特定のユーザープロファイルに存在することはないため、 別ユーザーやサービスから鍵コンテナを参照することができる。 CRYPT_MACHINE_KEYSETを単一で指定した場合はマシンコンテナのオープンとなり、 CRYPT_NEWKEYSETと共に指定した場合はマシンコンテナの作成となる。
CRYPT_DELETEKEYSET pszContainerに指定された名前の鍵コンテナを削除する。 pszContainerがNULLの場合は、デフォルトの鍵コンテナを削除する。 phProvにCSPのハンドルが返ることはないため、 CryptReleaseContextを呼び出す必要はない。
CRYPT_SILENT pszContainerに指定された名前の鍵コンテナの鍵に保護がかかっているような場合、 実際に鍵を使用する関数を呼び出すときにダイアログが表示される場合がある。 そのようなUIが表示されることが好まないアプリケーションは、 CRYPT_SILENTを指定する。 この定数が指定された場合、実際に鍵を使用する関数を呼び出しても、 ダイアログが表示されることはなく、エラーとしてNTE_SILENT_CONTEXTが返される。
CRYPT_VERIFYCONTEXT 特定の鍵コンテナをオープンせずにCSPのハンドルを取得する。 つまり、ハンドルを取得するにあたって、何らかの鍵コンテナが作成されている必要はない。 pszContainerにはNULLを指定するが、これはデフォルトの鍵コンテナを利用するという意味ではなく、 鍵コンテナの存在を考慮しないということである。 仮にCryptGenKeyで鍵ペアを作成しても、それは一時的なメモリに格納されるだけであり、 アプリケーションの終了時には開放される。 ただし、Windows Vistaからはこのような一時的な鍵ペアの作成は許可されていない。

CSPのハンドルは、不要になったらCryptReleaseContextで開放することになります。

BOOL WINAPI CryptReleaseContext(
  HCRYPTPROV hProv, 
  DWORD dwFlags 
);

hProvは、CSPのハンドルを指定します。 dwFlagsは、0を指定します。

次にCSPのハンドルを取得する例を示します。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCRYPTPROV hProv;

	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
		MessageBox(NULL, TEXT("CSPのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	MessageBox(NULL, TEXT("CSPのハンドルを取得しました。"), TEXT("OK"), MB_OK);

	CryptReleaseContext(hProv, 0);

	return 0;
}

まず、ハンドルの取得対象となるCSPの名前とプロバイダタイプを表す 第3引数と第4引数について説明します。 第3引数はMS_DEF_PROVのような定義を指定することができますが、NULLを指定することもよくあります。 この場合、第4引数に指定したプロバイダタイプを持つデフォルトのCSPが対象となるため、 今回の場合、実際に利用するCSPにはこだわりがないということになります。 実はこのような例は非常に多く、本来の目的が特定の暗号化アルゴリズムで暗号化を 行いたいというものであれば、どのCSPを利用しても望むべき結果を得ることができるはずですから、 その特定の暗号化アルゴリズムを含むプロバイダタイプを指定するだけで十分なのです。 PROV_RSA_FULLを指定して関数が成功したということは、 取得したCSPのハンドルを使ってRC4暗号やMD5ハッシュが行えることが保障されます。

続いて、鍵コンテナに関する引数について見ていきます。 第5引数にCRYPT_VERIFYCONTEXTを指定するということは、 鍵コンテナに格納されている鍵ペアを使用しないということを意味します。 よって、第2引数には鍵コンテナの名前を指定する必要がなく、NULLを指定します。 第5引数に0を指定した場合は、第2引数に指定した名前の鍵コンテナが作成されている必要があるため、 まだ鍵コンテナを作成しない現時点では、関数は失敗すると思われます。 また、0を指定してCSPのハンドルを取得できる場合でも、 鍵コンテナの鍵ペアを使用するつもりがないのであれば、 CRYPT_VERIFYCONTEXTを指定するようにしてください。 この定数を指定すると、そのアプリケーションは鍵ペアを利用できないわけですから、 CSPからすれば安全なアプリケーションのように見えることになります。

共有される鍵コンテナ

実際に鍵コンテナを作成することになった場合は、 使用するCSPの名前を明示的に指定したほうがよいと思われます。 NULLを指定することによってデフォルトCSPに鍵コンテナが作成された場合、 同じくNULLを指定して鍵コンテナから鍵ペアを取得することができるのは、 デフォルトCSPが決して変更されていないという前提を持たなければならないからです。 しかし、一部のCSPは鍵コンテナを完全に自身の管理下に置くようにはせず、 同系統と判断できる他のCSPからも鍵コンテナを参照できるようになっています。 この場合、鍵コンテナは特定のCSPのものではなく、 単に同系統のCSPによって共有されるものということになるでしょう。 次のコードは、MS_DEF_PROVが持っている全ての鍵コンテナを列挙しますが、 MS_DEF_PROVをMS_ENHANCED_PROVやMS_STRONG_PROVに変更しても、 結果が同じになることが分かります。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HCRYPTPROV hProv;
		DWORD      dwSize;
		TCHAR      szContainerName[256];
		BOOL       bResult;

		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		if (!CryptAcquireContext(&hProv, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
			return -1;
		
		dwSize = sizeof(szContainerName);
		bResult = CryptGetProvParam(hProv, PP_ENUMCONTAINERS, (LPBYTE)szContainerName, &dwSize, CRYPT_FIRST);
		for (; bResult;) {
			SendMessageA(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szContainerName);
			bResult = CryptGetProvParam(hProv, PP_ENUMCONTAINERS, (LPBYTE)szContainerName, &dwSize, CRYPT_NEXT);
		}

		CryptReleaseContext(hProv, 0);

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

CRYPT_NEWKEYSETで新しい鍵コンテナを作成した場合は、 再びこのコードを実行してみると面白いでしょう。 新しく作成した鍵コンテナの名前が列挙されるはずです。 また、CRYPT_VERIFYCONTEXTと共にCRYPT_MACHINE_KEYSETを指定すれば、 マシンコンテナを列挙することができます。 なお、鍵コンテナが共有されている理由についてですが、これは正確には共有というより、 DLLが同一であるから起こり得る現象です。 MS_DEF_PROVやMS_ENHANCED_PROVは互いに別々のCSPのように思えますが、 実際にはrsaenh.dllという同じDLLによって実装されているのです。



戻る