EternalWindows
CNG / KSPと証明書

前節では、RSAによる公開鍵暗号を行いましたが、 一般に公開鍵暗号でデータの暗号化と複合化に使われるのは、セッション鍵です。 公開鍵/秘密鍵が使われるのは、このセッション鍵を通信の両者で交換する際であり、 公開鍵で暗号化されたセッション鍵を秘密鍵で複合化して取得することになります。 鍵交換にはこれとは別に、DHやECDHのようなセッション鍵を作成するための情報を 交換する方法もありますが、今回は予めセッション鍵を作成しておき、 それを暗号化して送信する方法を考察します。

KSPにはアルゴリズムプロバイダにない機能の1つして、 エクスポート時における暗号化があります。 具体的には、NCryptExportKeyの第2引数に、実際にエクスポートする鍵とは別の鍵を指定することが可能で、 第1引数に指定した鍵は第2引数の鍵に暗号化されてエクスポートされることになります。 このため、第1引数にセッション鍵を指定すればよいように思えます。 また、NCryptExportKeyの第2引数にはECDHの鍵ペアを指定することができ、 楕円曲線による暗号化を行うことができます。 ECDHの鍵ペアはNCryptEncryptで利用できないため、この点は大きいといえます。 次にNCryptExportKeyの呼び出しを確認します。

NCryptExportKey(hSessionKey, hKey, lpszType, NULL, lpData, dwDataSize, &dwDataSize, 0);

このコードは、第2引数の鍵で第1引数のセッション鍵を暗号化しようとしていますが、 実際には1つの問題があります。 それは、KSPにはセッション鍵を作成する機能がないという点です。 アルゴリズムプロバイダやCryptoAPIではセッション鍵を作成することができますが、 当然ながらこれらの鍵のハンドルはNCryptExportKeyに指定することはできません。 また、鍵をバイトデータにエクスポートして、NCryptImportKeyでインポートする方法も成功しません。 よって、NCryptExportKeyを呼び出して、セッション鍵を暗号化することはできないと思われます。

さて、それではKSPで作成した鍵ペアでセッション鍵を暗号化できないのかというと、そのようなことはありません。 CryptoAPIにはセッション鍵を内部的に作成して使用する暗号化関数として、 CryptEncryptMessageがあります。 この関数は、証明書に含まれる公開鍵を使用してセッション鍵を暗号化しますが、 この公開鍵はKSPによって作成されたものでも問題なく動作します。 また、そのような公開鍵はECDHアルゴリズムのものでも構いません。 ECDHの公開鍵を含んだ証明書は、「公開キー」の部分が次のように表示されることになります。

見て分かるように、公開鍵の種類が楕円曲線暗号のECCとなっています。 256ビットとなっているのは、鍵ペアの作成時に指定したアルゴリズムが、 BCRYPT_ECDH_P256_ALGORITHMであるためです。

今回のプログラムは、ECDHの公開鍵が格納した証明書を作成し、 それをMY証明書ストアに追加します。 また、証明書を直ぐに公開できるようにファイルとしても保存します。

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

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

BOOL AddCertificateToStore(PCCERT_CONTEXT pContext);
BOOL SaveCertificate(LPTSTR lpszFileName, PCCERT_CONTEXT pContext);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	NCRYPT_PROV_HANDLE hProv;
	NCRYPT_KEY_HANDLE  hKey;
	SECURITY_STATUS    status;
	TCHAR              szSubjectAndIssuerName[] = TEXT("CN=ECDH256Cert Publisher");
	WCHAR              szKeyName[] = L"ECDH256Key";
	LPWSTR             lpszAlgId = BCRYPT_ECDH_P256_ALGORITHM;
	CERT_NAME_BLOB     subjectAndIssuerName;
	PCCERT_CONTEXT     pContext;
	DWORD              dwSize;
	LPBYTE             lpData;

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

	status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
	if (status != ERROR_SUCCESS) {
		status = NCryptCreatePersistedKey(hProv, &hKey, lpszAlgId, szKeyName, 0, 0);
		if (status != ERROR_SUCCESS) {
			NCryptFreeObject(hProv);
			return 0;
		}
		NCryptFinalizeKey(hKey, 0);
	}

	if (!CertStrToName(X509_ASN_ENCODING, szSubjectAndIssuerName, CERT_X500_NAME_STR, NULL, NULL, &dwSize, NULL)) {
		NCryptFreeObject(hKey);
		NCryptFreeObject(hProv);
		return 0;
	}
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	CertStrToName(X509_ASN_ENCODING, szSubjectAndIssuerName, CERT_X500_NAME_STR, NULL, lpData, &dwSize, NULL);
	
	subjectAndIssuerName.cbData = dwSize;
	subjectAndIssuerName.pbData = lpData;

	pContext = CertCreateSelfSignCertificate(hKey, &subjectAndIssuerName, 0, NULL, NULL, NULL, NULL, NULL);
	if (pContext == NULL) {
		MessageBox(NULL, TEXT("証明書コンテキストの作成に失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpData);
		NCryptFreeObject(hKey);
		NCryptFreeObject(hProv);
		return 0;
	}
	
	if (!AddCertificateToStore(pContext))
		MessageBox(NULL, TEXT("証明書を証明書ストアに追加できませんでした。"), NULL, MB_ICONWARNING);

	if (!SaveCertificate(TEXT("sample.cer"), pContext))
		MessageBox(NULL, TEXT("証明書の保存に失敗しました。"), NULL, MB_ICONWARNING);

	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);	

	HeapFree(GetProcessHeap(), 0, lpData);
	CertFreeCertificateContext(pContext);
	NCryptFreeObject(hKey);
	NCryptFreeObject(hProv);

	return 0;
}

BOOL AddCertificateToStore(PCCERT_CONTEXT pContext)
{
	HCERTSTORE hStore;

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL)
		return FALSE;

	if (!CertAddCertificateContextToStore(hStore, pContext, CERT_STORE_ADD_REPLACE_EXISTING, NULL)) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	return TRUE;
}

BOOL SaveCertificate(LPTSTR lpszFileName, PCCERT_CONTEXT pContext)
{
	HANDLE hFile;
	DWORD  dwWriteByte;
	
	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

	WriteFile(hFile, pContext->pbCertEncoded, pContext->cbCertEncoded, &dwWriteByte, NULL);
	
	CloseHandle(hFile);

	return TRUE;
}

このプログラムの内容は、自己署名入り証明書を作成する章のサンプルとほぼ同一です。 異なる点は、CertCreateSelfSignCertificateの第1引数にCSPのハンドルではなく、 KSPの鍵のハンドルを指定しているところです。 まず、NCryptOpenKeyで既にECDHの鍵ペアが作成されているかを確認し、 もし作成されていない場合は、NCryptCreatePersistedKeyにBCRYPT_ECDH_P256_ALGORITHMを指定して、 ECDHの鍵ペアを作成します。 そして、オープンされた鍵ペア、もしくは作成された鍵ペアがCertCreateSelfSignCertificateに 指定されることになります。 これにより、証明書に含まれる公開鍵のアルゴリズムはECDHになります。

証明書による暗号化

証明書を利用した暗号化には、前述の通りCryptEncryptMessageを呼び出すことになります。 次に、暗号化と複合化を実装したコードを示します。

#include <windows.h>

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

BOOL EncryptData(LPTSTR lpszFileName, PCCERT_CONTEXT pContext, LPBYTE lpData, DWORD dwDataSize);
BOOL DecryptData(LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize);
BOOL GetCertificateContextFromFile(LPTSTR lpszFileName, PCCERT_CONTEXT *pContext);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR          szFileName[] = TEXT("sample.dat");
	TCHAR          szCertFileName[] = TEXT("sample.cer");
	TCHAR          szData[] = TEXT("sample-data");
	BOOL           bEncrypt = TRUE;
	LPBYTE         lpData;
	DWORD          dwDataSize;
	PCCERT_CONTEXT pContext;

	if (bEncrypt) {
		if (GetCertificateContextFromFile(szCertFileName, &pContext)) {
			if (EncryptData(szFileName, pContext, (LPBYTE)szData, sizeof(szData)))
				MessageBox(NULL, TEXT("データを暗号化しました。"), TEXT("OK"), MB_OK);
			else
				MessageBox(NULL, TEXT("データの暗号化に失敗しました。"), NULL, MB_ICONWARNING);
			CertFreeCertificateContext(pContext);
		}
	}
	else {
		if (DecryptData(szFileName, &lpData, &dwDataSize)) {
			MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
			HeapFree(GetProcessHeap(), 0, lpData);
		}
		else
			MessageBox(NULL, TEXT("データの複合化に失敗しました。"), NULL, MB_ICONWARNING);
	}

	return 0;
}

BOOL EncryptData(LPTSTR lpszFileName, PCCERT_CONTEXT pContext, LPBYTE lpData, DWORD dwDataSize)
{
	CRYPT_ENCRYPT_MESSAGE_PARA encryptPara;
	CRYPT_ALGORITHM_IDENTIFIER encryptAlgorithm;
	LPBYTE                     lpEncryptedBlob;
	DWORD                      dwEncryptedBlobSize;
	HANDLE                     hFile;
	DWORD                      dwWriteByte;
	BOOL                       bResult;
	
	ZeroMemory(&encryptAlgorithm, sizeof(CRYPT_ALGORITHM_IDENTIFIER));
	encryptAlgorithm.pszObjId = szOID_NIST_AES128_CBC;

	encryptPara.cbSize                     = sizeof(CRYPT_ENCRYPT_MESSAGE_PARA);
	encryptPara.dwMsgEncodingType          = PKCS_7_ASN_ENCODING;
	encryptPara.hCryptProv                 = 0;
	encryptPara.ContentEncryptionAlgorithm = encryptAlgorithm;
	encryptPara.pvEncryptionAuxInfo        = NULL;
	encryptPara.dwFlags                    = 0;
	encryptPara.dwInnerContentType         = 0;
	
	bResult = CryptEncryptMessage(&encryptPara, 1, &pContext, lpData, dwDataSize, NULL, &dwEncryptedBlobSize);
	if (!bResult)
		return FALSE;
	
	lpEncryptedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptedBlobSize);
	
	bResult = CryptEncryptMessage(&encryptPara, 1, &pContext, lpData, dwDataSize, lpEncryptedBlob, &dwEncryptedBlobSize);
	if (!bResult) {
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		return FALSE;
	}

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, lpEncryptedBlob, dwEncryptedBlobSize, &dwWriteByte, NULL);
	CloseHandle(hFile);
	
	HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);

	return TRUE;
}

BOOL DecryptData(LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize)
{
	CRYPT_DECRYPT_MESSAGE_PARA decryptPara;
	HCERTSTORE                 hStore;
	LPBYTE                     lpEncryptedBlob;
	DWORD                      dwEncryptedBlobSize;
	LPBYTE                     lpData;
	DWORD                      dwDataSize;
	HANDLE                     hFile;
	DWORD                      dwReadByte;
	BOOL                       bResult;
	
	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;

	dwEncryptedBlobSize = GetFileSize(hFile, NULL);
	lpEncryptedBlob = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwEncryptedBlobSize);
	ReadFile(hFile, lpEncryptedBlob, dwEncryptedBlobSize, &dwReadByte, NULL);
	CloseHandle(hFile);

	hStore = CertOpenSystemStore(0, TEXT("MY"));
	if (hStore == NULL) {
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		return FALSE;
	}
	
	decryptPara.cbSize                   = sizeof(CRYPT_DECRYPT_MESSAGE_PARA);
	decryptPara.dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING;
	decryptPara.cCertStore               = 1;
	decryptPara.rghCertStore             = &hStore;

	bResult = CryptDecryptMessage(&decryptPara, lpEncryptedBlob, dwEncryptedBlobSize, NULL, &dwDataSize, NULL);
	if (!bResult) {
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
 
	bResult = CryptDecryptMessage(&decryptPara, lpEncryptedBlob, dwEncryptedBlobSize, lpData, &dwDataSize, NULL);
	if (!bResult) {
		HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
		HeapFree(GetProcessHeap(), 0, lpData);
		CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);
		return FALSE;
	}

	HeapFree(GetProcessHeap(), 0, lpEncryptedBlob);
	CertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG);

	*lplpData = lpData;
	*lpdwDataSize = dwDataSize;

	return TRUE;
}

BOOL GetCertificateContextFromFile(LPTSTR lpszFileName, PCCERT_CONTEXT *ppContext)
{
	HANDLE hFile;
	LPBYTE lpData;
	DWORD  dwSize;
	DWORD  dwReadByte;

	hFile = CreateFile(lpszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	
	dwSize = GetFileSize(hFile, NULL);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
	ReadFile(hFile, lpData, dwSize, &dwReadByte, NULL);
	
	*ppContext = CertCreateCertificateContext(X509_ASN_ENCODING, lpData, dwSize);

	HeapFree(GetProcessHeap(), 0, lpData);
	CloseHandle(hFile);

	return *ppContext != NULL;
}

bEncryptがTRUEの場合は、GetCertificateContextFromFileという関数で、 証明書ファイルから証明書コンテキストを取得し、 それをCryptEncryptMessageに指定することになります。 一方、bEncryptがFALSEの場合は、証明書を格納している証明書ストアのハンドルを指定し、 複合化の際に証明書に関連付けられた秘密鍵を利用することになります。 CryptEncryptMessageやCryptDecryptMessageの使い方については、 PKCS #7の章に記述されています。

KSPによって作成された公開鍵を証明書に格納できることから、 CertCreateSelfSignCertificateの他にKSPに対応している関数がいくつかあります。 たとえば、CryptImportPublicKeyInfoEx2は、証明書の公開鍵を基にハンドルを返します。

BCRYPT_KEY_HANDLE hKey;
LPBYTE            lpData;
DWORD             dwDataSize;

CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, &pContext->pCertInfo->SubjectPublicKeyInfo, 0, NULL, &hKey);

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

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

CryptImportPublicKeyInfoEx2に公開鍵を表すSubjectPublicKeyInfoメンバを指定すれば、 第5引数に公開鍵を表すハンドルが返ります。 これはBCRYPT_KEY_HANDLE型であるため、ハンドルを利用して呼び出せる関数もBCrypt関数となります。 この例では、BCryptGetPropertyにBCRYPT_ALGORITHM_NAMEを指定し、 公開鍵のアルゴリズムを取得しています。 証明書から公開鍵をエクスポートする場合は、CryptExportPublicKeyInfo(Ex)を呼び出します。



戻る