EternalWindows
スマートカード / スマートカードと暗号化

スマートカードに格納された鍵ペアは、PINによって保護されています。 たとえば、CryptSignHashは署名のために鍵ペアの秘密鍵にアクセスする必要がありますが、 このような場合にPINの入力を促すダイアログが表示されます。 また、CryptDecryptによる秘密鍵を用いたデータ複合化やCryptExportKeyによる 秘密鍵のエクスポート時にも、PINの入力が要求されることになるでしょう。 幸いにも、一度入力したPINはキャッシュされることが多いため、 秘密鍵へアクセスする関数を呼び出す度に、PINの入力を行うということはないはずです。 PINのキャッシュは、アプリケーションが終了するまで有効となっているのが普通です。

PINの入力にダイアログが表示されるという仕様は、 サービスのようなUIを望まないアプリケーションにとって煩わしいことであるといえます。 また、PINをファイルなどに保存しておき、それを読み取って自動でPINを入力したことにするという方法も妨げます。 ただし、CryptSetProvParamにPINを指定した場合は、話は別となります。

if (!CryptSetProvParam(hProv, PP_KEYEXCHANGE_PIN, (LPBYTE)szPin, 0)) {
	if (GetLastError() == SCARD_W_WRONG_CHV)
		MessageBox(NULL, TEXT("入力されたPINが正しくありません。"), NULL, MB_ICONWARNING); 
	return 0;
}

CryptSetProvParamの第2引数にPP_KEYEXCHANGE_PINまたはPP_SIGNATURE_PINを指定した場合、 第3引数にPINを指定することができます。 このPINが正しい場合は、指定したPINがキャッシュされることになるため、 秘密鍵へのアクセスが生じる関数を呼び出しても、 ダイアログが表示されることはなくなります。

今回のプログラムは、スマートカードに格納された鍵ペアを利用して、 データの暗号化と複合化を行います。 szPinに指定する文字列は、ベンダー提供のドキュメントに記述されているPINを指定することになります。 鍵ペアは、前節のプログラムで作成しています。

#include <windows.h>

BOOL EncryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize, LPBYTE lpBuffer, DWORD dwBufferSize);
BOOL DecryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpBuffer, DWORD dwBufferSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCRYPTPROV hProv;
	HCRYPTKEY  hKey;
	TCHAR      szBuf[256];
	TCHAR      szFileName[] = TEXT("sample.dat");
	TCHAR      szData[] = TEXT("sample-data");
	CHAR       szPin[] = "";
	DWORD      dwKeySpec = AT_KEYEXCHANGE;
	BOOL       bEncrypt = TRUE;
	BOOL       bAutoInputPin = FALSE;

	if (!CryptAcquireContext(&hProv, TEXT("\\\\.\\ReaderName\\MyContainer), TEXT("Vendor CSP Name"), PROV_RSA_FULL, 0))
		return 0;

	if (!CryptGetUserKey(hProv, dwKeySpec, &hKey)) {
		CryptReleaseContext(hProv, 0);
		return 0;
	}
	
	if (bEncrypt) {
		if (EncryptData((HCRYPTKEY)hKey, szFileName, (LPBYTE)szData, sizeof(szData), (LPBYTE)szBuf, sizeof(szBuf))) 
			MessageBox(NULL, TEXT("暗号化しました。"), TEXT("OK"), MB_OK);
	}
	else {
		if (bAutoInputPin) {
			if (!CryptSetProvParam(hProv, PP_KEYEXCHANGE_PIN, (LPBYTE)szPin, 0)) {
				if (GetLastError() == SCARD_W_WRONG_CHV)
					MessageBox(NULL, TEXT("指定されたPINが正しくありません。"), NULL, MB_ICONWARNING); 
				return 0;
			}
		}
		if (DecryptData((HCRYPTKEY)hKey, szFileName, (LPBYTE)szBuf, sizeof(szBuf)))
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

	CryptDestroyKey(hKey);
	CryptReleaseContext(hProv, 0);
	
	return 0;
}

BOOL EncryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize, LPBYTE lpBuffer, DWORD dwBufferSize)
{
	HANDLE hFile;
	DWORD  dwWriteByte;
	BOOL   bResult;

	if (dwDataSize > dwBufferSize)
		return FALSE;
	
	CopyMemory(lpBuffer, lpData, dwDataSize);
	bResult = CryptEncrypt(hKey, 0, TRUE, 0, lpBuffer, &dwDataSize, dwBufferSize);
	if (!bResult)
		return FALSE;
	
	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpBuffer, dwDataSize, &dwWriteByte, NULL);
	CloseHandle(hFile);

	return TRUE;
}

BOOL DecryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpBuffer, DWORD dwBufferSize)
{
	HANDLE hFile;
	DWORD  dwReadByte;
	DWORD  dwDataSize;
	BOOL   bResult;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

	dwDataSize = GetFileSize(hFile, NULL);
	if (dwDataSize > dwBufferSize) {
		CloseHandle(hFile);
		return FALSE;
	}

	ReadFile(hFile, lpBuffer, dwDataSize, &dwReadByte, NULL);
	CloseHandle(hFile);

	bResult = CryptDecrypt(hKey, 0, TRUE, 0, lpBuffer, &dwDataSize);
	if (!bResult) {
		if (GetLastError() == SCARD_W_CANCELLED_BY_USER)
			MessageBox(NULL, TEXT("PIN入力がキャンセルされました。"), NULL, MB_ICONWARNING);
	}

	return bResult;
}

bEncryptをTRUEで実行した場合は、暗号化されたデータがファイルに保存され、 FALSEとして実行した場合は、ファイルから読み取ったデータが複合化されます。 また、bAutoInputPinがTRUEである場合は、PINが自動で指定されることになります。 PIN入力が生じるのは、CryptGetUserKeyによって鍵ペアのハンドルを取得する際ではなく、 自作関数のDecryptData内部でCryptDecryptが呼ばれる際であるため、 この前にCryptSetProvParamの呼び出しが行われています。 なお、CryptSetProvParamでPINの入力に失敗した場合は、 リトライカウンタ(PINを入力できる残り回数)を知ることができないため、この点は注意してください。

MS_SCARD_PROVによる暗号化

次節では、MS_SCARD_PROVというCSPを使用して鍵コンテナを作成しています。 そこで作成した鍵コンテナの鍵ペアを利用して、暗号化を行うコードを次に示します。

#include <windows.h>

BOOL IsExistContainer(LPTSTR lpszProvName, LPTSTR lpszContainerName);
BOOL EncryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize, LPBYTE lpBuffer, DWORD dwBufferSize);
BOOL DecryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpBuffer, DWORD dwBufferSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HCRYPTPROV hProv;
	HCRYPTKEY  hKey;
	TCHAR      szProvName[] = MS_SCARD_PROV;
	TCHAR      szContainerName[] = TEXT("MyContainer");
	TCHAR      szFileName[] = TEXT("sample.dat");
	TCHAR      szData[] = TEXT("sample-data");
	TCHAR      szBuf[256];
	CHAR       szPin[] = "";
	DWORD      dwKeySpec = AT_KEYEXCHANGE;
	BOOL       bEncrypt = TRUE;
	BOOL       bAutoInputPin = FALSE;

	if (!CryptAcquireContext(&hProv, NULL, szProvName, PROV_RSA_FULL, CRYPT_DEFAULT_CONTAINER_OPTIONAL)) {
		if (GetLastError() == SCARD_E_NO_SERVICE)
			MessageBox(NULL, TEXT("Smart Cardサービスが起動されていません。"), NULL, MB_ICONWARNING);
		else if (GetLastError() == SCARD_E_NO_READERS_AVAILABLE)
			MessageBox(NULL, TEXT("カードリーダが接続されていません。"), NULL, MB_ICONWARNING);
		else if (GetLastError() == SCARD_W_CANCELLED_BY_USER)
			MessageBox(NULL, TEXT("カードがセットされませんでした。"), NULL, MB_ICONWARNING);
		else
			;
		return 0;
	}
	
	CryptReleaseContext(hProv, 0);

	if (IsExistContainer(szProvName, szContainerName)) {
		if (!CryptAcquireContext(&hProv, szContainerName, szProvName, PROV_RSA_FULL, 0)) {
			MessageBox(NULL, TEXT("CSPのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
	}
	else {
		MessageBox(NULL, TEXT("指定された鍵コンテナが存在しません。"), NULL, MB_ICONWARNING);
		return 0;
	}

	if (!CryptGetUserKey(hProv, dwKeySpec, &hKey)) {
		CryptReleaseContext(hProv, 0);
		return 0;
	}
	
	if (bEncrypt) {
		if (EncryptData((HCRYPTKEY)hKey, szFileName, (LPBYTE)szData, sizeof(szData), (LPBYTE)szBuf, sizeof(szBuf))) 
			MessageBox(NULL, TEXT("暗号化しました。"), TEXT("OK"), MB_OK);
	}
	else {
		if (bAutoInputPin) {
			if (!CryptSetProvParam(hProv, PP_KEYEXCHANGE_PIN, (LPBYTE)szPin, 0)) {
				if (GetLastError() == SCARD_W_WRONG_CHV)
					MessageBox(NULL, TEXT("指定されたPINが正しくありません。"), NULL, MB_ICONWARNING); 
				return 0;
			}
		}
		if (DecryptData((HCRYPTKEY)hKey, szFileName, (LPBYTE)szBuf, sizeof(szBuf)))
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

	CryptDestroyKey(hKey);
	CryptReleaseContext(hProv, 0);

	return 0;
}

BOOL IsExistContainer(LPTSTR lpszProvName, LPTSTR lpszContainerName)
{
	BOOL       bResult;
	HCRYPTPROV hProv;

	bResult = CryptAcquireContext(&hProv, lpszContainerName, lpszProvName, PROV_RSA_FULL, CRYPT_SILENT);
	if (bResult)
		CryptReleaseContext(hProv, 0);

	return bResult;
}

BOOL EncryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize, LPBYTE lpBuffer, DWORD dwBufferSize)
{
	HANDLE hFile;
	DWORD  dwWriteByte;
	BOOL   bResult;

	if (dwDataSize > dwBufferSize)
		return FALSE;
	
	CopyMemory(lpBuffer, lpData, dwDataSize);
	bResult = CryptEncrypt(hKey, 0, TRUE, 0, lpBuffer, &dwDataSize, dwBufferSize);
	if (!bResult)
		return FALSE;
	
	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpBuffer, dwDataSize, &dwWriteByte, NULL);
	CloseHandle(hFile);

	return TRUE;
}

BOOL DecryptData(HCRYPTKEY hKey, LPTSTR lpszFileName, LPBYTE lpBuffer, DWORD dwBufferSize)
{
	HANDLE hFile;
	DWORD  dwReadByte;
	DWORD  dwDataSize;
	BOOL   bResult;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

	dwDataSize = GetFileSize(hFile, NULL);
	if (dwDataSize > dwBufferSize) {
		CloseHandle(hFile);
		return FALSE;
	}

	ReadFile(hFile, lpBuffer, dwDataSize, &dwReadByte, NULL);
	CloseHandle(hFile);

	bResult = CryptDecrypt(hKey, 0, TRUE, 0, lpBuffer, &dwDataSize);
	if (!bResult) {
		if (GetLastError() == SCARD_W_CANCELLED_BY_USER)
			MessageBox(NULL, TEXT("PIN入力がキャンセルされました。"), NULL, MB_ICONWARNING);
	}

	return bResult;
}

このプログラムは、既に示したプログラムと異なり、 CSP名がMS_SCARD_PROVで固定化され、鍵コンテナには名前だけを指定しています。 カードベンダーがCSPではなく、Card MiniDriverは公開している場合は、 MS_SCARD_PROVというCSPを利用することになります。 詳細は、次節で記述しています。



戻る