EternalWindows
CSP / 公開鍵暗号

公開鍵/秘密鍵における暗号化は、セッション鍵を利用した暗号化と同じ要領で行うことができます。 つまり、暗号化ではCryptEncryptを呼び出し、 複合化の際にはCryptDecryptを呼び出すことになります。 少し気がかりとなるのは、鍵ペアのハンドルをこれらの関数に指定した場合、 一体どちらの鍵で暗号化や複合化が行われるのかという点ですが、 これは公開鍵/秘密鍵の性質を考えてみると分かりやすいと思われます。 公開鍵は不特定多数の相手に公開されるものであり、 この鍵を使用すれば秘密鍵で暗号化されたデータを復号化することができます。 これが意味することは、同じく不特定多数の者がデータを復号できることを意味しますから、 暗号化には秘密鍵ではなく公開鍵を用いるべきです。 このようにすれば、暗号化されたデータを複合化できるのは、秘密鍵の所有者のみになります。 CryptEncryptに鍵ペアのハンドルを指定した場合、内部で公開鍵を用いて暗号化が行われ、 CryptDecryptの場合は、鍵ペアの秘密鍵で複合化が行われます。 ただし、基本的にはCryptEncryptに指定するのは鍵ペアのハンドルではなく、 鍵ペアの所有者によってエクスポートされた公開鍵を指定することになるでしょう。

公開鍵による暗号化は、セッション鍵による暗号化と比べて速度が低速であるという欠点があります。 また、秘密鍵の所有者側から公開鍵を持つ相手に対して、 第三者によって複合化されないデータを送信するにはどうしたらよいのでしょうか。 これらの問題に対する答えは、公開鍵とセッション鍵の交換にあります。 以前、セッション鍵を利用した通信について説明した際、 セッション鍵を傍受されてしまっては、データが複合化されるという問題を指摘しましたが、 このセッション鍵を公開鍵で暗号化して送信したらどうでしょうか。 これならば、暗号化されたセッション鍵を複合化できるのは秘密鍵の所有者だけですから、 その所有者だけがセッション鍵を手に入れることができるはずです。 ネットワークアプリケーション等のマニュアルでは、 通信時における暗号化の仕様をRSA + RC4のように記述することがありますが、 これは正に公開鍵暗号と共通鍵暗号(セッション鍵による暗号)を利用することを意味しています。 RSAは公開鍵暗号の一種ですし、RC4はセッション鍵で利用できるストリーム暗号です。

鍵ペアを所有する側をサーバーとし、公開鍵を受け取る側をクライアントとして、 上記の動作をどのように行うのかを考えてみたいと思います。 まず、サーバーはクライアントに公開鍵を公開します。 これを取得したクライアントはセッション鍵を作成し、 そのセッション鍵を受け取った公開鍵で暗号化してサーバーに送信します。 そして、サーバーはその暗号化されたセッション鍵を秘密鍵で複合化します。 これで、どちらもセッション鍵を持つことになったため、 後はそれを使用してどちらも暗号化と複合化を行えることができます。 次に、コード例を示します。

BOOL ImportSessionKey(HANDLE hPipe, HCRYPTPROV hProv, HCRYPTKEY *phSessionKey)
{
	BYTE      keyData[1024];
	DWORD     dwKeySize;
	DWORD     dwWriteByte;
	DWORD     dwReadByte;
	HCRYPTKEY hKey;

	if (!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hKey))
		return FALSE;

	CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, keyData, &dwKeySize);
	WriteFile(hPipe, keyData, dwKeySize, &dwWriteByte, NULL);
	
	ReadFile(hPipe, keyData, sizeof(keyData), &dwReadByte, NULL);
	CryptImportKey(hProv, keyData, dwReadByte, hKey, 0, phSessionKey);

	return TRUE;
}

BOOL CreateAndExportSessionKey(HANDLE hPipe, HCRYPTPROV hProv, ALG_ID algid, HCRYPTKEY *phPubKey, HCRYPTKEY *phSessionKey)
{
	BYTE      keyData[1024];
	DWORD     dwKeySize;
	DWORD     dwWriteByte;
	DWORD     dwReadByte;
	HCRYPTKEY hPubKey;
	HCRYPTKEY hSessionKey;
	
	ReadFile(hPipe, keyData, sizeof(keyData), &dwReadByte, NULL);
	CryptImportKey(hProv, keyData, dwReadByte, 0, 0, &hPubKey);

	if (!CryptGenKey(hProv, algid, CRYPT_EXPORTABLE, &hSessionKey))
		return FALSE;

	CryptExportKey(hSessionKey, hPubKey, SIMPLEBLOB, 0, keyData, &dwKeySize);
	WriteFile(hPipe, keyData, dwKeySize, &dwWriteByte, NULL);
	
	*phPubKey = hPubKey;
	*phSessionKey = hSessionKey;	

	return TRUE;
}

ImportSessionKeyがサーバー側の関数、 CreateAndExportSessionKeyがクライアント側の関数とします。 また、通信に使うのは同期モードの名前付きパイプであるとします。 まず、サーバーはCryptGetUserKeyを呼び出してAT_KEYEXCHANGEの鍵ペアを取得します。 セッション鍵でのエクスポート時やインポート時に暗号化や複合化が行われる関係上、 鍵ペアの用途はAT_KEYEXCHANGEでなければなりません。 次に、サーバーはCryptExportKeyの第3引数にPUBLICKEYBLOBを指定して、 公開鍵のバイナリデータをエクスポートし、 それをWriteFileでクライアントに送信します。 クライアントは、CreateAndExportSessionKeyのReadFileでこのデータを取得し、 CryptImportKeyを呼び出して公開鍵のハンドルを取得します。 そして、次にセッション鍵の作成に入るのですが、この部分に少し注目したいと思います。

CryptGenKey(hProv, algid, CRYPT_EXPORTABLE, &hSessionKey);

これまで、CryptGenKeyの第2引数にはAT_KEYEXCHANGEやAT_SIGNATUREを指定して、 特定の用途を持った鍵ペアを作成できることを説明してきましたが、 実は第2引数にCALG_RC4のような暗号化アルゴリズムを指定した場合、 CryptGenKeyはセッション鍵を作成することになります。 セッション鍵の作成にはCryptDeriveKeyという関数もありますが、 この関数とCryptGenKeyによるセッション鍵の作成の違いは、 パスワードを必要とするかどうかです。 CryptGenKeyはパスワードを要求しませんから、 この関数で作成されたセッション鍵は再度作成することができず、 正にクライアントとサーバーの両方が起動している セッションの間だけ有効ということになります。 セッション鍵をエクスポートする場合は、CRYPT_EXPORTABLEを指定します。

セッション鍵の作成が終われば、それを公開鍵で暗号化しなければなりません。 CryptExportKeyの第1引数はエクスポートすべきセッション鍵を指定し、 第2引数にはそれを暗号化すべき公開鍵を指定します。 また、セッション鍵をエクスポートするときには、第3引数にSIMPLEBLOBを指定します。 WriteFileの呼び出しによって、暗号化されたセッション鍵がサーバーに送られ、 ImportSessionKeyのReadFileは制御を返すことになります。 CryptImportKeyで暗号化されたセッション鍵をインポートする際、 第4引数に鍵ペアのハンドルを指定します。 これにより、鍵ペアの秘密鍵で複合化が行われます。

CryptoAPIとSSPI

サーバーとクライアント間で送受信するデータを暗号化し、 さらに接続段階で認証を必要とする場合は、 CryptoAPIではなくSSPIを利用した方が効率的であるといえます。 CryptoAPIがCSPに対してのインターフェースであるのと同じように、 SSPIはSSPに対してのインターフェースであり、 各種プロバイダの詳細を意識せずに済むという点も同一です。 SSPは独自の認証プロトコルと既存の暗号化アルゴリズムを実装し、 アプリケーションは主に認証という視点から使用するSSPを選択します。 たとえば、Kerberos認証を行いたい場合はKerberos SSPを選択し、 SSL認証を行いたい場合はSChannel SSPを選択します。



戻る