EternalWindows
CNG / アルゴリズムプロバイダ

CNGからアルゴリズムプロバイダの機能を利用するためには、 BCryptで始まる関数を呼び出すことになります。 このような関数は、カーネルモードのドライバからも呼び出すことが可能であり、 ユーザーモードアプリケーションで記述したコードは、 特に加工せずにドライバで利用することができます。 次に示すBCryptOpenAlgorithmProviderは、 アルゴリズムプロバイダのハンドルを返します。

NTSTATUS WINAPI BCryptOpenAlgorithmProvider(
  BCRYPT_ALG_HANDLE* phAlgorithm,
  LPCWSTR pszAlgId,
  LPCWSTR pszImplementation,
  DWORD dwFlags
)

phAlgorithmは、アルゴリズムプロバイダのハンドルを受け取る変数のアドレスを指定します。 pszAlgIdは、使用するアルゴリズムの名前を指定します。 pszImplementationは、使用するアルゴリズムプロバイダの名前を指定します。 既存のアルゴリズムプロバイダを表すMS_PRIMITIVE_PROVIDERという定数が存在しますが、 多くの場合はNULLを指定しpszAlgIdのアルゴリズムをサポートする デフォルトのアルゴリズムプロバイダを選択しようとします。 dwFlagsは、通常は使用しないため0を指定します。

BCryptOpenAlgorithmProviderは、使用するアルゴリズムプロバイダを決定しますが、 それと共に使用するアルゴリズムも決定されることに注意してください。 たとえば、pszAlgIdに"RC4"を指定した場合、これは暗号化アルゴリズムですから、 phAlgorithmを使用して行えることは、RC4の暗号化だけということになります。 CNGの関数には、暗号化に使う鍵やハッシュオブジェクトを作成する関数がありますが、 それらの関数にアルゴリズムを指定するようなことはありません。

不要になったアルゴリズムプロバイダのハンドルは、 BCryptCloseAlgorithmProviderで閉じることになります。

NTSTATUS WINAPI BCryptCloseAlgorithmProvider(
  BCRYPT_ALG_HANDLE hAlgorithm,
  ULONG dwFlags
);

hAlgorithmは、アルゴリズムプロバイダのハンドルを指定します。 dwFlagsは、現在は使用されないため0を指定します。

CNGでは、アルゴリズムプロバイダや鍵などがオブジェクトとして扱われ、 それをBCRYPT_ALG_HANDLEやBCRYPT_KEY_HANDLEのようなハンドルで表現することになっています。 ハンドルからオブジェクトに関する情報を取得したい場合は、 BCryptGetPropertyを呼び出します。

NTSTATUS WINAPI BCryptGetProperty(
  BCRYPT_HANDLE hObject,
  LPCWSTR pszProperty,
  PUCHAR pbOutput,
  ULONG cbOutput,
  ULONG* pcbResult,
  ULONG dwFlags
);

hObjectは、オブジェクトを表すハンドルを指定します。 pszPropertyは、オブジェクトからどのような情報を取得するかを表す文字列を指定します。 このような情報はプロパティと呼ばれ、数多くの専用の定義が存在します。 pbOutputは、プロパティを受け取るバッファを指定します。 cbOutputは、pbOutputに指定したバッファのサイズを指定します。 pcbResultは、バッファにコピーしたサイズを受け取る変数のアドレスを指定します。 pbOutputにNULLを指定した場合は、バッファのサイズが返ります。 dwFlagsは、現在は使用されないため0を指定します。

今回のプログラムは、アルゴリズムプロバイダを取得する例を示しています。 bcrypt.dllの関数を利用するため、bcrypt.hのインクルードとbcrypt.libへのリンクが必要になります。

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

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BCRYPT_ALG_HANDLE hAlg;
	NTSTATUS          status;
	LPBYTE            lpData;
	DWORD             dwDataSize;
	
	status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RC4_ALGORITHM, NULL, 0);
	if (!NT_SUCCESS(status))
		return 0;
	
	BCryptGetProperty(hAlg, BCRYPT_ALGORITHM_NAME, NULL, 0, &dwDataSize, 0);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	BCryptGetProperty(hAlg, BCRYPT_ALGORITHM_NAME, lpData, dwDataSize, &dwDataSize, 0);

	MessageBoxW(NULL, (LPWSTR)lpData, L"OK", MB_OK);

	HeapFree(GetProcessHeap(), 0, lpData);
	BCryptCloseAlgorithmProvider(hAlg, 0);

	return 0;
}

まずは、NT_SUCCESSというマクロについて説明します。 このマクロは、NTSTATUS型の値が成功を表しているかを確認するもので、 主にドライバが呼び出す関数の戻り値に対して使用されます。 BCryptの関数は、ドライバからも呼び出せることになっているため、 その戻り値はNTSTATUS型であり、ユーザーモードのアプリケーションから実行する場合でも、 NT_SUCCESSマクロで関数の成否を確認するのが原則といえます。 ただし、このマクロはWDK(Windows Driver Kit)に含まれるヘッダーファイルに定義されているため、 WDKがインストールされていない環境では、明示的にマクロを定義することになります。 さらに補足すると、NTSTATUS型が取り得るSTATUS_SUCCESSやSTATUS_INVALID_PARAMETERなどの定数は、 bcrypt.hには定義されていません。 単純に関数の成否を確認するのではなく、どのような原因で関数が失敗したかを特定したい場合は、 ヘッダーファイルのインクルードを次のようにします。

#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>
#include <bcrypt.h>

windows.hの前にntstatus.hをインクルードし、その後にWIN32_NO_STATUSを定義します。 こうすることで、NTSTATUS型が取り得る定数を参照することができます。

BCryptOpenAlgorithmProviderは、次のように呼び出されています。

status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RC4_ALGORITHM, NULL, 0);
if (!NT_SUCCESS(status))
	return 0;

第2引数にBCRYPT_RC4_ALGORITHMを指定しているため、 RC4の暗号化をサポートするアルゴリズムプロバイダを取得しようとしています。 "RC4"と直接指定することもできますが、既知のアルゴリズムには専用の定義が存在するため、 それを利用することにしています。 NT_SUCCESSのマクロの結果がFALSEである場合は、関数が失敗したことを意味するため、 その場合はアプリケーションを終了します。 続いて、BCryptGetPropertyの呼び出しを確認します。

BCryptGetProperty(hAlg, BCRYPT_ALGORITHM_NAME, NULL, 0, &dwDataSize, 0);
lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
BCryptGetProperty(hAlg, BCRYPT_ALGORITHM_NAME, lpData, dwDataSize, &dwDataSize, 0);

MessageBoxW(NULL, (LPWSTR)lpData, TEXT("OK"), MB_OK);

今回は、オブジェクトに設定されたアルゴリズム名を取得するため、 BCRYPT_ALGORITHM_NAMEを指定しています。 1回目の呼び出しでは、バッファのサイズが分からないため第3引数にNULLを指定し、 第5引数で必要なサイズを取得します。 その後、バッファを確保し、2回目の呼び出しでバッファの中身を初期化します。 CNGでは、アルゴリズム名などの文字列が全てUNICODEで扱われているため、 文字列を表示する場合はUNICODE版の関数を呼び出す必要があります。

プロバイダの列挙

システムに存在するプロバイダをアプリケーションから確認したい場合は、 BCryptEnumRegisteredProvidersを呼び出すことができます。 この関数は、アルゴリズムプロバイダとKSPを共に列挙します。

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

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

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: {
		WCHAR               szBuf[256];
		ULONG               i;
		ULONG               uBufferSize = 0;
		PCRYPT_PROVIDERS    pProviders = NULL;
		PCRYPT_PROVIDER_REG pProviderReg;

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

		BCryptEnumRegisteredProviders(&uBufferSize, &pProviders);

		for (i = 0; i < pProviders->cProviders; i++) {
			uBufferSize = 0;
			pProviderReg = NULL;
			BCryptQueryProviderRegistration(pProviders->rgpszProviders[i], CRYPT_UM, 0, &uBufferSize, &pProviderReg);
			
			wsprintfW(szBuf, L"%s (%s)", pProviders->rgpszProviders[i], pProviderReg->pUM->pszImage);
			SendMessageW(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			
			BCryptFreeBuffer(pProviderReg);
		}
		
		BCryptFreeBuffer(pProviders);

		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);
}

BCryptEnumRegisteredProvidersは、第2引数にPCRYPT_PROVIDERS型で識別できるバッファを返します。 このバッファには、各種プロバイダの名前が含まれているため、 これをリストボックスに追加することになります。 また、プロバイダの名前があれば、BCryptQueryProviderRegistrationでプロバイダの情報を取得できるため、 PCRYPT_PROVIDER_REG型で識別できるバッファを取得し、 プロバイダのファイル名を追加しています。 取得したバッファは、BCryptFreeBufferで開放することになります。

実際に上記のコードを実行してみると分かることですが、 Microsoft Primitive Providerの実体はbcrypt.dllになっています。 bcrypt.dllは、アプリケーションからアルゴリズムプロバイダへの仲介として機能する一方、 それ自身がアルゴリズムプロバイダでもあるため、 実際に主要なアルゴリズムを一通り実装しているのです。 アルゴリズムプロバイダは、GetChiperInterfaceなどのインターフェース名を含んだ関数をエクスポートし、 そこで自身が実装する関数の関数ポインタを返すことになっていますが、 この傾向はbcrypt.dllも例外ではありません。 つまり、bcrypt.dllはBCrypt関数の他にGetChiperInterfaceなどの関数もエクスポートしています。



戻る