EternalWindows
CNG / 署名と検証

今回は、KSPにおける署名と検証の方法を説明します。 基本的な流れは、アルゴリズムプロバイダのときと同様であるため、 それほど複雑ではありません。 署名は、NCryptSignHashで行います。

SECURITY_STATUS WINAPI NCryptSignHash(
  NCRYPT_KEY_HANDLE hKey,
  VOID *pPaddingInfo,
  PBYTE pbHashValue,
  DWORD cbHashValue,
  PBYTE pbSignature,
  DWORD cbSignature,
  DWORD *pcbResult,
  DWORD dwFlags
);

hKeyは、署名に使用する鍵のハンドルを指定します。 pPaddingInfoは、パディング情報を格納するバッファを指定します。 不要な場合は、NULLを指定することができます。 pbHashValueは、ハッシュ値を格納したバッファを格納します。 cbHashValueは、pbHashValueのサイズを指定します。 pbSignatureは、作成された署名を受け取るバッファを指定します。 cbSignatureは、pbSignatureのサイズを指定します。 pcbResultは、データをバッファにコピーしたサイズを受け取る変数のアドレスを指定します。 pbSignatureにNULLを指定した場合は、必要なバッファのサイズが返ります。 dwFlagsは基本的に0を指定しますが、パディングに関する定数を指定することもできます。

検証を行うには、NCryptVerifySignatureを呼び出します。

SECURITY_STATUS WINAPI NCryptVerifySignature(
  NCRYPT_KEY_HANDLE hKey,
  VOID *pPaddingInfo,
  PBYTE pbHashValue,
  DWORD cbHashValue,
  PBYTE pbSignature,
  DWORD cbSignature,
  DWORD dwFlags
);

hKeyは、検証に使用する鍵のハンドルを指定します。 pPaddingInfoは、パディング情報を格納したバッファを指定します。 pbHashValueは、ハッシュ値を格納したバッファを指定します。 cbHashValueは、pbHashValueのサイズを指定します。 pbSignatureは、署名を格納したバッファを指定します。 cbSignatureは、pbSignatureのサイズを指定します。 dwFlagsは、NCryptSignHashで指定したものと同一の値を指定します。

検証時に公開鍵をインポートする必要がある場合は、 NCryptImportKeyを呼び出すことになります。

SECURITY_STATUS WINAPI NCryptImportKey(
  NCRYPT_PROV_HANDLE hProvider,
  NCRYPT_KEY_HANDLE hImportKey,
  LPCWSTR pszBlobType,
  NCryptBufferDesc *pParameterList,
  NCRYPT_KEY_HANDLE *phKey,
  PBYTE pbData,
  DWORD cbData,
  DWORD dwFlags
);

hProviderは、KSPのハンドルを指定します。 hImportKeyは、インポートする鍵が暗号化されている場合に、 それを複合化するための鍵のハンドルを指定します。 不要な場合は、NULLを指定することができます。 pszBlobTypeは、pbDataがどのような形式なのかを表す定数を指定します。 pParameterListは、パラメータ情報を格納したバッファを指定します。 phKeyは、インポートされた鍵のハンドルを受け取る変数のアドレスを指定します。 pbDataは、インポートするデータを格納したバッファを指定します。 cbDataは、pbDataのサイズを指定します。 dwFlagsは、NCryptCreatePersistedKeyやNCryptFinalizeKeyに指定できる定数を指定します。 不要な場合は、0で問題ありません。

今回のプログラムは、署名したデータをファイルに保存します。 また、ファイルからデータを取得し、署名に問題がないかを検証します。 事前に前節のプログラムを実行して鍵ペアを作成しておいてください。

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

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

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

BOOL SignData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize);
BOOL VerifyData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize);
BOOL ImportKeyData(NCRYPT_PROV_HANDLE hProv, LPTSTR lpszFileName, LPWSTR lpszType, NCRYPT_KEY_HANDLE *phKey);
BOOL GetHashData(LPBYTE lpData, DWORD dwDataSize, LPBYTE *lplpHashData, LPDWORD lpdwHashDataSize);

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

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

	if (bSign) {
		status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
		if (status == ERROR_SUCCESS) {
			if (SignData(hKey, szFileName, (LPBYTE)szData, sizeof(szData)))
				MessageBox(NULL, TEXT("署名しました。"), TEXT("OK"), MB_OK);
		}
	}
	else {
		if (ImportKeyData(hProv, szPubKeyName, BCRYPT_ECCPUBLIC_BLOB, &hKey)) {
			if (VerifyData(hKey, szFileName, &lpData, &dwDataSize)) {
				MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
				HeapFree(GetProcessHeap(), 0, lpData);
			}
		}
	}
	
	NCryptFreeObject(hKey);
	NCryptFreeObject(hProv);

	return 0;
}

BOOL SignData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE lpData, DWORD dwDataSize)
{
	HANDLE          hFile;
	LPBYTE          lpHashData;
	LPBYTE          lpSignature;
	DWORD           dwHashDataSize;
	DWORD           dwSignatureSize;
	DWORD           dwWriteByte;
	SECURITY_STATUS status;

	if (!GetHashData(lpData, dwDataSize, &lpHashData, &dwHashDataSize))
		return FALSE;
	
	NCryptSignHash(hKey, NULL, (LPBYTE)lpHashData, dwHashDataSize, NULL, 0, &dwSignatureSize, 0);
	lpSignature = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignatureSize);
	status = NCryptSignHash(hKey, NULL, (LPBYTE)lpHashData, dwHashDataSize, lpSignature, dwSignatureSize, &dwSignatureSize, 0);
	if (status != ERROR_SUCCESS) {
		HeapFree(GetProcessHeap(), 0, lpHashData);
		HeapFree(GetProcessHeap(), 0, lpSignature);
		return FALSE;
	}

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	WriteFile(hFile, &dwDataSize, sizeof(DWORD), &dwWriteByte, NULL);
	WriteFile(hFile, &dwSignatureSize, sizeof(DWORD), &dwWriteByte, NULL);
	WriteFile(hFile, lpData, dwDataSize, &dwWriteByte, NULL);
	WriteFile(hFile, lpSignature, dwSignatureSize, &dwWriteByte, NULL);
	CloseHandle(hFile);
	
	HeapFree(GetProcessHeap(), 0, lpHashData);
	HeapFree(GetProcessHeap(), 0, lpSignature);

	return TRUE;
}

BOOL VerifyData(NCRYPT_KEY_HANDLE hKey, LPTSTR lpszFileName, LPBYTE *lplpData, LPDWORD lpdwDataSize)
{
	HANDLE          hFile;
	LPBYTE          lpData;
	LPBYTE          lpHashData;
	LPBYTE          lpSignature;
	DWORD           dwDataSize;
	DWORD           dwHashDataSize;
	DWORD           dwSignatureSize;
	DWORD           dwReadByte;
	SECURITY_STATUS status;

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

	ReadFile(hFile, &dwDataSize, sizeof(DWORD), &dwReadByte, NULL);
	ReadFile(hFile, &dwSignatureSize, sizeof(DWORD), &dwReadByte, NULL);
	lpData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwDataSize);
	lpSignature = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignatureSize);
	ReadFile(hFile, lpData, dwDataSize, &dwReadByte, NULL);
	ReadFile(hFile, lpSignature, dwSignatureSize, &dwReadByte, NULL);
	CloseHandle(hFile);
	
	if (!GetHashData(lpData, dwDataSize, &lpHashData, &dwHashDataSize)) {
		HeapFree(GetProcessHeap(), 0, lpData);
		HeapFree(GetProcessHeap(), 0, lpSignature);
		CloseHandle(hFile);
		return FALSE;
	}

	status = NCryptVerifySignature(hKey, NULL, lpHashData, dwHashDataSize, lpSignature, dwSignatureSize, 0);

	*lplpData = lpData;
	*lpdwDataSize = dwDataSize;

	HeapFree(GetProcessHeap(), 0, lpHashData);
	HeapFree(GetProcessHeap(), 0, lpSignature);

	return status == ERROR_SUCCESS;
}

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

BOOL GetHashData(LPBYTE lpData, DWORD dwDataSize, LPBYTE *lplpHashData, LPDWORD lpdwHashDataSize)
{
	BCRYPT_ALG_HANDLE  hAlg;
	BCRYPT_HASH_HANDLE hHash;
	DWORD              dwResult;
	DWORD              dwHashObjectSize;
	LPBYTE             lpHashObject;
	NTSTATUS           status;

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

	BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (LPBYTE)&dwHashObjectSize, sizeof(DWORD), &dwResult, 0);
	lpHashObject = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashObjectSize);
	status = BCryptCreateHash(hAlg, &hHash, lpHashObject, dwHashObjectSize, NULL, 0, 0);
	if (!NT_SUCCESS(status)) {
		HeapFree(GetProcessHeap(), 0, lpHashObject);
		BCryptCloseAlgorithmProvider(hAlg, 0);
		return FALSE;
	}
	
	BCryptHashData(hHash, lpData, dwDataSize, 0);

	BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (LPBYTE)lpdwHashDataSize, sizeof(DWORD), &dwResult, 0);
	*lplpHashData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwHashDataSize);
	BCryptFinishHash(hHash, *lplpHashData, *lpdwHashDataSize, 0);

	HeapFree(GetProcessHeap(), 0, lpHashObject);
	BCryptDestroyHash(hHash);
	BCryptCloseAlgorithmProvider(hAlg, 0);
	
	return TRUE;
}

このプログラムとアルゴリズムプロバイダによる署名プログラムと比較した場合、 最大の違いは秘密鍵の参照方法です。 アルゴリズムプロバイダでは、秘密鍵をファイルからインポートしていましたが、 KSPの場合はNCryptOpenKeyでハンドルを取得することができます。

if (bSign) {
	status = NCryptOpenKey(hProv, &hKey, szKeyName, 0, 0);
	if (status == ERROR_SUCCESS) {
		if (SignData(hKey, szFileName, (LPBYTE)szData, sizeof(szData)))
			MessageBox(NULL, TEXT("署名しました。"), TEXT("OK"), MB_OK);
	}
}
else {
	if (ImportKeyData(hProv, szPubKeyName, BCRYPT_ECCPUBLIC_BLOB, &hKey)) {
		if (VerifyData(hKey, szFileName, &lpData, &dwDataSize)) {
			MessageBox(NULL, (LPTSTR)lpData, TEXT("OK"), MB_OK);
			HeapFree(GetProcessHeap(), 0, lpData);
		}
	}
}

このようにNCryptOpenKeyで鍵ペアを取得できるのは、 KSPによって鍵ペアが管理されているからに他なりません。 これにより、アプリケーションの運用は効率的かつ安全となります。 ちなみに、同一コンピュータ上でbSignをFALSEにして実行する場合でも、 NCryptOpenKeyを呼び出すことができます。 鍵ペアのハンドルは、NCryptSignHashの場合には秘密鍵に適応され、 NCryptVerifySignatureの場合は公開鍵に適応されます。

その他のコードについては、呼び出している関数がncrypt.dllのものになっただけで、 基本的にはアルゴリズムプロバイダによる署名と同一です。 ハッシュ値の生成では、bcrypt.dllのBCryptCreateHashなどを呼び出していますが、 ncrypt.dllにはこれに対応する関数がないため、 アルゴリズムプロバイダ、もしくはCryptoAPIを使用することになるでしょう。 なお、bSignがFALSEの場合は公開鍵をインポートしているわけですが、 このような処理はbcrypt.dllのBCryptImportKeyPairでも可能です。 つまり、NCryptSignHashで作成された署名をBCryptVerifySignatureで検証することができます。 これは、アルゴリズムプロバイダによる署名プログラムで実際に行ってみると分かります。

秘密鍵のエクスポート

秘密鍵をエクスポートしたい場合は、NCryptExportKeyにBCRYPT_ECCPRIVATE_BLOBを指定しますが、 デフォルトでは秘密鍵はエクスポート可能ではありません。 秘密鍵をエクスポート可能にするには、NCryptFinalizeKeyで鍵ペアを完成させておく前に 次のようなコードを実行します。

DWORD dwFlags = NCRYPT_ALLOW_EXPORT_FLAG;
NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&dwFlags, sizeof(DWORD), 0);

NCryptCreatePersistedKeyを呼び出した後に、 上記のようなコードが実行されるようにします。 NCryptSetPropertyは鍵のプロパティを設定する関数であり、 第2引数にNCRYPT_EXPORT_POLICY_PROPERTYを指定すれば、 第3引数にエクスポートに関するフラグを指定できるようになります。 これをNCRYPT_ALLOW_EXPORT_FLAGと指定することで、 鍵ペアの秘密鍵がエクスポート可能となります。 秘密鍵がエクスポート可能になることで、証明書をPFX形式としてエクスポートすることもできます。



戻る