EternalWindows
スマートカード / スマートカードとKSP

Windows Vistaから登場した新しい暗号化APIであるCNGには、 アルゴリズムプロバイダとKSPという2種類のプロバイダが存在します。 このうち、KSPはCryptoAPIのCSPのように鍵ペアを保存する機能を提供しており、 それはスマートカードにも対応します。 つまり、KSPにもMS_SCARD_PROVに相当するプロバイダが含まれています。 こうした事を考慮した場合、カードベンダーがCard MiniDriverを作成することは非常に重要といえるでしょう。 これにより、アプリケーションはカードへのアクセスするAPIとして、 CryptoAPIとCNGのどちらかも選択できる柔軟性が生じます。

今回のプログラムは、KSPを使用してスマートカードに鍵ペアを作成します。 利用するスマートカードに対応するCard MiniDriverがインストールされていることを前提とします。

#include <windows.h>
#include <ncrypt.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	NCRYPT_PROV_HANDLE hProv;
	NCRYPT_KEY_HANDLE  hKey;
	SECURITY_STATUS    status;
	WCHAR              szKeyName[] = L"RSAKey";
	LPWSTR             lpszAlgId = BCRYPT_RSA_ALGORITHM;

	status = NCryptOpenStorageProvider(&hProv, MS_SMART_CARD_KEY_STORAGE_PROVIDER, 0);
	if (status != ERROR_SUCCESS)
		return 0;

	status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, NCRYPT_SILENT_FLAG);
	if (status == ERROR_SUCCESS) {
		if (MessageBox(NULL, TEXT("指定された名前の鍵ペアが既に存在します。削除しますか?"), TEXT("確認"), MB_YESNO) == IDYES)
			NCryptDeleteKey(hKey, 0);
		else
			NCryptFreeObject(hKey);
		NCryptFreeObject(hProv);
		return 0;
	}
	
	status = NCryptCreatePersistedKey(hProv, &hKey, lpszAlgId, szKeyName, 0, 0);
	if (status != ERROR_SUCCESS) {
		NCryptFreeObject(hProv);
		return 0;
	}

	status = NCryptFinalizeKey(hKey, 0);
	if (status != ERROR_SUCCESS) {
		if (status == SCARD_E_NO_SERVICE)
			MessageBox(NULL, TEXT("Smart Cardサービスが起動されていません。"), NULL, MB_ICONWARNING);
		else if (status == SCARD_E_NO_READERS_AVAILABLE)
			MessageBox(NULL, TEXT("カードリーダが接続されていません。"), NULL, MB_ICONWARNING);
		else if (status == SCARD_W_CANCELLED_BY_USER)
			MessageBox(NULL, TEXT("処理がキャンセルされました。"), NULL, MB_ICONWARNING);
		else
			;
		NCryptFreeObject(hKey);
		NCryptFreeObject(hProv);
		return 0;
	}
	
	MessageBox(NULL, TEXT("鍵ペアを作成しました。"), TEXT("OK"), MB_OK);

	NCryptFreeObject(hKey);
	NCryptFreeObject(hProv);
	
	return 0;
}

KSPによる鍵の保存方法は、CSPのそれとは多少異なっています。 CSPでは、まず鍵コンテナというものを作成し、 その中に署名用の鍵ペアと鍵交換用の鍵ペアを作成しましたが、 KSPには鍵コンテナや鍵の用途という概念がありません。 1つの鍵ペアに名前を付けて、それを保存することになります。 このため、KSPのハンドルを取得するNCryptOpenStorageProviderに、 鍵コンテナの名前を受け取る引数は含まれていません。 MS_SMART_CARD_KEY_STORAGE_PROVIDERが、スマートカード用のKSPとなります。

NCryptOpenKeyは、第3引数に指定した名前をもった鍵ペアのハンドルを取得します。 鍵ペアが存在しない場合は、MS_SCARD_PROVと同様にダイアログの問題が生じるため、 第5引数にNCRYPT_SILENT_FLAGを指定しています。 NCryptCreatePersistedKeyは、作成したい鍵ペアの情報を指定する関数であり、 実際に鍵ペアを作成するわけではありません。 鍵ペアは、NCryptFinalizeKeyによって作成されることになります。 このため、この関数の呼び出しでは、カードのセットを促すダイアログや、 PIN入力を促すダイアログが表示されることになります。

MS_SMART_CARD_KEY_STORAGE_PROVIDERによる暗号化

MS_SMART_CARD_KEY_STORAGE_PROVIDERによって作成された鍵ペアを利用するコードを次に示します。 鍵ペアのアルゴリズムはBCRYPT_RSA_ALGORITHMを指定したため、暗号化を行う処理となっています。

#include <windows.h>
#include <ncrypt.h>

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

BOOL IsExistKey(NCRYPT_PROV_HANDLE hProv, LPWSTR lpszKeyName);
BOOL EncryptData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize);
BOOL DecryptData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	NCRYPT_PROV_HANDLE hProv;
	NCRYPT_KEY_HANDLE  hKey;
	LPBYTE             lpData;
	DWORD              dwDataSize;
	SECURITY_STATUS    status;
	WCHAR              szKeyName[] = L"RSAKey";
	TCHAR              szFileName[] = TEXT("sample.dat");
	TCHAR              szData[] = TEXT("sample-data");
	WCHAR              szPin[] = L"";
	BOOL               bEncrypt = TRUE;
	BOOL               bAutoInputPin = FALSE;

	status = NCryptOpenStorageProvider(&hProv, MS_SMART_CARD_KEY_STORAGE_PROVIDER, 0);
	if (status != ERROR_SUCCESS)
		return 0;

	if (IsExistKey(hProv, szKeyName)) {
		status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
		if (status != ERROR_SUCCESS)
			return 0;
	}
	else {
		MessageBox(NULL, TEXT("指定された鍵ペアが存在しません。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	if (bEncrypt) {
		if (EncryptData(hKey, szFileName, (LPBYTE)szData, sizeof(szData)))
			MessageBox(NULL, TEXT("暗号化しました。"), TEXT("OK"), MB_OK);
	}
	else {
		if (bAutoInputPin) {
			NCryptSetProperty(hKey, NCRYPT_PIN_PROPERTY, (LPBYTE)szPin, sizeof(szPin), 0);
			if (GetLastError() == ERROR_INVALID_PARAMETER) {
				MessageBox(NULL, TEXT("指定されたPINが正しくありません。"), NULL, MB_ICONWARNING); 
				return 0;
			}
		}

		if (DecryptData(hKey, szFileName, &lpData, &dwDataSize)) {
			MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
			HeapFree(GetProcessHeap(), 0, lpData);
		}
	}
	
	NCryptFreeObject(hKey);
	NCryptFreeObject(hProv);

	return 0;
}

BOOL IsExistKey(NCRYPT_PROV_HANDLE hProv, LPWSTR lpszKeyName)
{
	NCRYPT_KEY_HANDLE hKey;
	SECURITY_STATUS   status;
	
	status = NCryptOpenKey(hProv, &hKey, lpszKeyName, 0, NCRYPT_SILENT_FLAG);
	if (status == ERROR_SUCCESS)
		NCryptFreeObject(hKey);

	return status == ERROR_SUCCESS;
}

BOOL EncryptData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize)
{
	HANDLE          hFile;
	DWORD           dwWriteByte;
	DWORD           dwResult;
	DWORD           dwEncryptDataSize;
	LPBYTE          lpEncryptData;
	SECURITY_STATUS status;

	NCryptEncrypt(hKey, lpData, dwDataSize, NULL, NULL, 0, &dwEncryptDataSize, NCRYPT_PAD_PKCS1_FLAG);
	lpEncryptData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptDataSize);
	status = NCryptEncrypt(hKey, lpData, dwDataSize, NULL, lpEncryptData, dwEncryptDataSize, &dwResult, NCRYPT_PAD_PKCS1_FLAG);
	if (status != ERROR_SUCCESS) {
		HeapFree(GetProcessHeap(), 0, lpEncryptData);
		return FALSE;
	}

	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(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize)
{
	HANDLE          hFile;
	DWORD           dwReadByte;
	DWORD           dwEncryptDataSize;
	LPBYTE          lpEncryptData;
	SECURITY_STATUS status;

	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);
	
	status = NCryptDecrypt(hKey, lpEncryptData, dwEncryptDataSize, NULL, NULL, 0, lpdwDataSize, NCRYPT_PAD_PKCS1_FLAG);
	if (status != ERROR_SUCCESS) {
		HeapFree(GetProcessHeap(), 0, lpEncryptData);
		return FALSE;
	}
	*lplpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwDataSize);
	NCryptDecrypt(hKey, lpEncryptData, dwEncryptDataSize, NULL, *lplpData, *lpdwDataSize, lpdwDataSize, NCRYPT_PAD_PKCS1_FLAG);

	HeapFree(GetProcessHeap(), 0, lpEncryptData);

	return TRUE;
}

NCryptOpenStorageProviderには、MS_SMART_CARD_KEY_STORAGE_PROVIDERを指定します。 鍵ペアのハンドルはNCryptOpenKeyで取得することになりますが、 その前にNCRYPT_SILENT_FLAGを指定して鍵ペアの存在を確認しておきます。 この意味については、既に述べてきた通りです。 暗号化されたデータを複合化する場合には、秘密鍵が使用されることになるため、 NCryptDecryptでPINの入力を促すダイアログが表示されるはずです。

bAutoInputPinがTRUEの場合、PINの自動入力を行うことになります。 これは、NCryptSetPropertyにNCRYPT_PIN_PROPERTYを指定することで可能になります。 これが成功した場合、PINはキャッシュされることになり、 秘密鍵を利用する関数を呼び出しても、ダイアログが表示されることはなくなります。 注意しなければならないのは、指定するPINがUNICODE文字列である点と、 PINの入力が正しいかどうかに関わらず、 NCryptSetPropertyがERROR_SUCCESSを返す点です。 このため、GetLastErrorを通じて入力の成否を確認することになります。



戻る