EternalWindows
CSP / セッション鍵

CryptoAPIで暗号化を行うためには、鍵というものが必要になります。 鍵には、セッション鍵と呼ばれるタイプと公開鍵/秘密鍵と呼ばれる鍵ペアのタイプがあり、 両者の最大の違いは、暗号と復号の際に同一の鍵を使用するかどうかです。 セッション鍵で暗号化したデータを復号するには、 暗号化に使用したセッション鍵が必要であり、暗号と復号に同一の鍵を使用します。 このような暗号方式は、共通鍵暗号と呼ばれることがあります。 一方、公開鍵/秘密鍵の鍵ペアを使用する場合は、 公開鍵で暗号化したデータは、対応する秘密鍵でしか復号できません。 また、秘密鍵で暗号化したデータは対応する公開鍵でしか復号できません。 このような暗号方式は、公開鍵暗号と呼ばれることがあります。

セッション鍵と公開鍵/秘密鍵の鍵ペアのもう1つの違いとして、鍵の運用方法があります。 公開鍵/秘密鍵の鍵ペアはCSPの鍵コンテナに格納されているため、 CryptoAPIを通じていつでも取得することができますが、 セッション鍵はCSPによって鍵コンテナに格納されることはありません。 ここで生じる問題は、セッション鍵で暗号化したデータを復号するとなったときに、 どうやって暗号化の際に使用したセッション鍵を取得すればよいのかという点です。 セッション鍵を作成するCryptDeriveKeyは、この問題の解決策として、 鍵のパスワードという概念を用いています。 つまり、ある1つのパスワードから作成される鍵を常に同一にすることにより、 そのパスワードを覚えている限りでは、常に同じセッション鍵を得ることができるようになるわけです。

BOOL WINAPI CryptDeriveKey(
  HCRYPTPROV hProv, 
  ALG_ID Algid, 
  HCRYPTHASH hBaseData, 
  DWORD dwFlags, 
  HCRYPTKEY *phKey 
);

hProvは、CSPのハンドルを指定します。 Algidは、暗号化アルゴリズムを示すIDを指定します。 hBaseDataは、ハッシュオブジェクトのハンドルを指定します。 CryptDeriveKeyは、パスワードを文字列としてではなくハッシュ値として受け取るため、 このハッシュオブジェクトはパスワードから作成されたハッシュ値を持っている必要があります。 dwFlagsは、上位16ビットに作成する鍵のサイズを指定し、 下位16ビットに鍵の作成に関するフラグを指定します。 phKeyは、作成されたセッション鍵のハンドルが返ります。

よく知られる暗号化アルゴリズムとして、ストリーム暗号(CALG_RC4など)とブロック暗号(CALG_RC2など)がありますが、 この両者の違いを知ることは、ときとして重要な意味を持ちます。 ストリーム暗号は、与えられたデータを1バイトずつ暗号化していく方式で、 その1バイトを復号化する際には、他のバイトの値は必要ありません。 つまり、暗号化されたデータの一部分が欠落するようなことがあっても、 その欠落した部分以外は復号することができることになります。 一方、ブロック暗号ではデータを一定のブロック単位で暗号化するため、 ブロック内のデータが少しでも欠落すれば、 そのデータは正しく復号できないことになります。 暗号の強度としては、ブロック暗号の方が優れていますが、 速度重視の場合は1バイト単位のストリーム暗号を利用するのがよいと思われます。

dwFlagsの上位16ビットには鍵のサイズを指定することになっていますが、 理想となるサイズはどれぐらいの値なのでしょうか。 これについて考える前にまず覚えておかなければならないのは、 CSPと暗号化アルゴリズムによって、 鍵の最小値と最大値、そしてデフォルト値が異なるという点です。 たとえば、MS_DEF_PROVのCALG_RC4による鍵のサイズの最大値は56ですが、 MS_ENHANCED_PROVでのCALG_RC4による鍵のサイズの最大値は128となっています。 特定のCSPに依存するコードを書きたくないような場合は、鍵のサイズを0に指定し、 デフォルト値が設定されるようにすることもよくあります。 なお、CSPがサポートする鍵のサイズは、 CryptGetProvParamにPP_ENUMALGS_EXを指定することで取得できます。 また、作成した鍵に設定されたサイズ(ソルト値は考慮しない)は、 CryptGetKeyParamにKP_KEYLENを指定することで確認できます。

続いて、dwFlagsの下位16ビットに指定できる定数の一部を見てみます。

定数 意味
CRYPT_EXPORTABLE 鍵をファイルに保存したり、他のコンピュータに送信したい場合に指定する。 これにより、CryptExportKeyで鍵のバイナリデータを取得することができる。
CRYPT_CREATE_SALT SALT(ソルト値)は、鍵の一部を一定サイズのランダム値で構成する。 ソルト値を変更することにより、同一の鍵でデータを暗号化するとき、 異なる結果を得ることができる。 このフラグを指定すると必ずソルト値が作成されるようになるが、 指定しない場合でも作成されることがある。
CRYPT_NO_SALT 鍵にソルト値を追加しない。
CRYPT_UPDATE_KEY 作成済みのセッション鍵をハッシュ値を基に更新したい場合に指定する。 マイクロソフト社製のCSPはこの定数をサポートしない。

実際に、サイズとフラグをどのように設定すればよいかを考えてみましょう。 まず、サイズはDWORD型の上位16ビットに相当するということから、 たとえば、サイズを128にしたいときに0x0080と指定するわけにはいきません。 これでは、下位16ビットの値が0x0080になってしまいます。 よって、16ビットに左にシフトした値である0x0080000を指定することになります。 これに併せてフラグも指定したい場合は、次のように論理和を指定することになるでしょう。

CryptDeriveKey(hProv, algid, hHash, 0x0080000 | CRYPT_EXPORTABLE, phKey);

このように指定すれば、この鍵のサイズは128ビットとなり、 さらにエクスポート可能なフラグが設定されることになります。

不要になった鍵は、CryptDestroyKeyで破棄することになります。

BOOL WINAPI CryptDestroyKey(
  HCRYPTKEY hKey 
);

hKeyは、破棄したい鍵のハンドルを指定します。 ここで述べている破棄とは、鍵に要していたメモリを開放するという意味であり、 鍵ペアのハンドルを指定しても、 鍵コンテナに格納されている公開鍵/非公開鍵が削除されるようなことはありません。

次に示すCreateSessionKeyという自作関数は、セッション鍵の作成手順を示しています。 この関数の呼び出し側は、暗号化と複合化の際に同じセッション鍵が得られるように、 lpDataに共通のパスワードを指定することになるでしょう。

BOOL CreateSessionKey(HCRYPTPROV hProv, LPBYTE lpData, DWORD dwDataSize, HCRYPTKEY *phKey)
{
	BOOL       bResult;
	HCRYPTHASH hHash;

	if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash))
		return FALSE;
	
	CryptHashData(hHash, lpData, dwDataSize, 0);
	
	bResult = CryptDeriveKey(hProv, CALG_RC4, hHash, 0, phKey);
	
	CryptDestroyHash(hHash);

	return bResult;
}

CryptDeriveKeyに指定するパスワードはハッシュオブジェクトを通じて指定しなければならないため、 まずCryptCreateHashでハッシュオブジェクトを作成します。 ここでのハッシュアルゴリズムはCALG_MD5となっていますが、これは自由に決めて構いません。 そして、CryptHashDataでパスワードのハッシュ値をハッシュオブジェクトに設定します。 CryptDeriveKeyの第4引数が0ということは、上位16ビットが0で、下位16ビットも0ということになります。 つまり、鍵のサイズをデフォルトにし、鍵に対して特別なフラグを設定しないことを意味します。 次節は、この関数が返した鍵を使って暗号化を行います。

ハッシュ化されたパスワードの応用

CryptDeriveKeyに指定するパスワードを事前にハッシュ化しなければならないという仕様は、 開発者にとって非常に煩わしいことだと思われます。 しかし、ファイルやネットワーク等の外部からパスワードを取得するような場合、 それがクリアテキストとして保存されたり送られたりはしませんから、 取得したデータはパスワードのハッシュ値となっているはずです。 このため、CryptDeriveKeyもパスワードのハッシュ値を受け取る必要があります。 CryptDeriveKeyにはハッシュ値をハッシュオブジェクト経由で指定するため、 事前にCryptSetHashParamでハッシュ値を設定しておかなければなりません。

CryptSetHashParam(hHash, HP_HASHVAL, lpData, 0);

第1引数は、CryptCreateHashで作成されたハッシュオブジェクトのハンドルです。 第2引数はハッシュオブジェクトに設定するデータの種類を表し、 HP_HASHVALは第3引数のデータがハッシュ値であることを示しています。 第3引数は、ハッシュ化されたデータを指定します。 第4引数は、常に0を指定します。



戻る