EternalWindows
CNG / 公開鍵暗号とRSA

KSPで暗号化を行うには、NCryptEncryptを呼び出します。 アルゴリズムプロバイダのように初期化ベクトルという概念はありませんが、 基本的な使い方は変わりません。

SECURITY_STATUS WINAPI NCryptEncrypt(
  NCRYPT_KEY_HANDLE hKey,
  PBYTE pbInput,
  DWORD cbInput,
  VOID *pPaddingInfo,
  PBYTE pbOutput,
  DWORD cbOutput,
  DWORD *pcbResult,
  DWORD dwFlags
);

hKeyは、暗号化に使用する鍵のハンドルを指定します。 pbInputは、暗号化したいデータを格納したバッファを指定します。 cbInputは、pbInputのサイズを指定します。 pPaddingInfoは、パディング情報を格納したバッファを指定します。 pbOutputは、暗号化されたデータを受け取るバッファを指定します。 cbOutputは、pbOutputのサイズを指定します。 pcbResultは、バッファにコピーしたデータのサイズを受け取る変数のアドレスを指定します。 pbOutputがNULLの場合は、バッファのサイズが返ります。 dwFlagsは、パディングに関する定数を指定します。

複合化は、NCryptDecryptを呼び出します。

SECURITY_STATUS WINAPI NCryptDecrypt(
  NCRYPT_KEY_HANDLE hKey,
  PBYTE pbInput,
  DWORD cbInput,
  VOID *pPaddingInfo,
  PBYTE pbOutput,
  DWORD cbOutput,
  DWORD *pcbResult,
  DWORD dwFlags
);

hKeyは、複合化に使用する鍵のハンドルを指定します。 pbInputは、複合化したいデータを格納したバッファを指定します。 cbInputは、pbInputのサイズを指定します。 pPaddingInfoは、パディング情報を格納したバッファを指定します。 pbOutputは、複合化されたデータを受け取るバッファを指定します。 cbOutputは、pbOutputのサイズを指定します。 pcbResultは、バッファにコピーしたデータのサイズを受け取る変数のアドレスを指定します。 pbOutputがNULLの場合は、バッファのサイズが返ります。 dwFlagsは、NCryptEncryptと同一の値を指定します。

暗号化を行うには、暗号化アルゴリズムが設定された鍵ペアが必要になります。 前々節のプログラムの一部を以下のコードに置き換えて実行し、 事前に鍵ペアを作成しておいてください。

WCHAR  szKeyName[] = L"RSAKey";
TCHAR  szFileName[] = TEXT("rsa-pubkey.dat");
LPWSTR lpszAlgId = BCRYPT_RSA_ALGORITHM;
LPWSTR lpszType = BCRYPT_RSAPUBLIC_BLOB;

pszAlgIdにBCRYPT_RSA_ALGORITHMを指定していることから分かるように、 RSA公開鍵暗号化アルゴリズムが設定されることになります。 また、公開鍵をエクスポートする形式もRSA用のBCRYPT_RSAPUBLIC_BLOBを指定しています。

今回のプログラムは、データを暗号化してそれをファイルに保存します。 また、保存された暗号化データを複合化するコードも含まれています。

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

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

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

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              szPubKeyName[] = TEXT("rsa-pubkey.dat");
	TCHAR              szFileName[] = TEXT("sample.dat");
	TCHAR              szData[] = TEXT("sample-data");
	BOOL               bEncrypt = TRUE;

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

	if (bEncrypt) {
		if (ImportKeyData(hProv, szPubKeyName, BCRYPT_RSAPUBLIC_BLOB, &hKey)) {
			if (EncryptData(hKey, szFileName, (LPBYTE)szData, sizeof(szData)))
				MessageBox(NULL, TEXT("暗号化しました。"), TEXT("OK"), MB_OK);
		}
	}
	else {
		status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
		if (status == ERROR_SUCCESS) {
			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 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;
}

BOOL ImportKeyData(NCRYPT_PROV_HANDLE hProv, LPTSTR lpszFileName, LPWSTR lpszType, NCRYPT_KEY_HANDLE *phKey)
{
	HANDLE          hFile;
	DWORD           dwReadByte;
	DWORD           dwDataSize;
	LPBYTE          lpData;
	SECURITY_STATUS status;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	
	dwDataSize = GetFileSize(hFile, NULL);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	ReadFile(hFile, lpData, dwDataSize, &dwReadByte, NULL);
	CloseHandle(hFile);
	
	status = NCryptImportKey(hProv, NULL, lpszType, NULL, phKey, lpData, dwDataSize, 0);
	
	HeapFree(GetProcessHeap(), 0, lpData);

	return status == ERROR_SUCCESS;
}

このプログラムでは暗号化を公開鍵で行い、複合化を秘密鍵で行っています。 このため、bEncryptがTRUEである場合は、公開鍵をインポートし、 NCryptEncryptを呼び出す必要があります。

if (bEncrypt) {
	if (ImportKeyData(hProv, szPubKeyName, BCRYPT_RSAPUBLIC_BLOB, &hKey)) {
		if (EncryptData(hKey, szFileName, (LPBYTE)szData, sizeof(szData)))
			MessageBox(NULL, TEXT("暗号化しました。"), TEXT("OK"), MB_OK);
	}
}
else {
	status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
	if (status == ERROR_SUCCESS) {
		if (DecryptData(hKey, szFileName, &lpData, &dwDataSize)) {
			MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
			HeapFree(GetProcessHeap(), 0, lpData);
		}
	}
}

bEncryptがFALSEである場合は、秘密鍵でデータを複合化する必要があるので、 NCryptOpenKeyを呼び出すことになります。 鍵ペアのハンドルをNCryptDecryptに指定した場合、それは秘密鍵に適応されることになります。 また、公開鍵による暗号化を鍵ペアを持つローカルコンピュータで実行する場合は、 ファイルから公開鍵をインポートするのではなく、 NCryptOpenKeyで鍵ペアのハンドルを取得することができます。 鍵ペアのハンドルをNCryptEncryptに指定した場合、それは公開鍵に適応されることになります。

NCryptEncryptやNCryptDecryptは、BCryptEncryptやBCryptDecryptのように 初期化ベクトルを指定することはできませんが、それ以外の部分は基本的に同一となります。 ただし、RSAによる暗号化の場合は、最後の引数にNCRYPT_PAD_PKCS1_FLAGを指定しなければ 関数が失敗することに注意してください。

RSA鍵ペアとCryptoAPI

KSPで作成したRSAの鍵ペアは、NCryptFinalizeKeyにNCRYPT_WRITE_KEY_TO_LEGACY_STORE_FLAGを指定することで、 CryptoAPIからも利用できるようになります。 具体的には、NCryptCreatePersistedKeyで指定した鍵の名前と同名の鍵コンテナがCSPに作成され、 CryptGetUserKeyで取得できるようになります。

NCryptCreatePersistedKey(hProv, &hKey, BCRYPT_RSA_ALGORITHM, L"CNG-RSAKey", AT_KEYEXCHANGE, 0);
NCryptFinalizeKey(hKey, NCRYPT_WRITE_KEY_TO_LEGACY_STORE_FLAG);

NCryptCreatePersistedKeyの第3引数にBCRYPT_RSA_ALGORITHMを指定すれば、RSAの鍵ペアが作成されることになるため、 NCryptFinalizeKeyにNCRYPT_WRITE_KEY_TO_LEGACY_STORE_FLAGを指定することができます。 これにより、CSPに"CNG-RSAKey"という名前の鍵コンテナが作成されます。 第5引数には、0ではなくAT_KEYEXCHANGEまたはAT_SIGNATUREを指定する必要があります。 CryptoAPIにおける鍵の用途はこの2つでしかなく、 CryptGetUserKeyに0を指定することはできないためです。 なお、"CNG-RSAKey"という名前の鍵は、NCryptOpenKeyで取得して利用することもできますが、 これをNCryptDeleteKeyで削除することは直ちに成功しません。 この場合は、まず関連する名前のCSP鍵コンテナを削除しておく必要があります。 次に、KSPによって作成されたRSAの鍵ペアを利用するCryptoAPIの例を示します。

#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");
	BOOL       bEncrypt = TRUE;

	if (!CryptAcquireContext(&hProv, TEXT("CNG-RSAKey"), NULL, PROV_RSA_FULL, 0))
		return 0;

	if (!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &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 (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);

	return bResult;
}

このコードは、通常のCryptoAPIのプログラムと変わることころはありません。 単純にCryptAcquireContextに指定する鍵コンテナの名前が、 NCryptCreatePersistedKeyに指定した鍵の名前となっているだけです。 また、CryptGetUserKeyに指定する鍵の用途についても同じ要領です。 CryptGetUserKeyで取得した鍵ペアは、CryptEncryptの場合は公開鍵に適応され、 CryptDecryptの場合は秘密鍵に適応されます。 鍵の用途がAT_SIGNATUREである場合は、暗号化を行えないことに注意してください。

CSPと鍵コンテナとKSPの鍵が同名である場合、 NCryptTranslateHandleという関数を呼び出すことによって、 CSPのハンドルからKSPのハンドルを取得することができます。 先に示したように、NCRYPT_WRITE_KEY_TO_LEGACY_STORE_FLAGを指定してRSAの鍵ペアを作成している場合は、 この関数は問題なく成功することになります。

if (!CryptAcquireContext(&hLegacyProv, TEXT("CNG-RSAKey"), NULL, PROV_RSA_FULL, 0))
	return 0;

status = NCryptTranslateHandle(&hProv, &hKey, hLegacyProv, NULL, AT_KEYEXCHANGE, 0);
if (status != ERROR_SUCCESS)
	return 0;

まず、CryptAcquireContextでCSPのハンドルを取得し、 これをNCryptTranslateHandleの第3引数に指定します。 さらに、第5引数に鍵の用途を指定します。 ただし、第4引数にCSPの鍵ペアのハンドルを指定している場合は、0を指定しても問題ありません。 関数が成功した場合、第1引数にKSPのハンドルが返り、 第2引数にKSPで利用できる鍵ペアが返ります。



戻る