1.簡単な暗号化
ここでは、ストリーミング暗号化について解説していますが、ブロック暗号化の解説[http://www.trustss.co.jp/smnEasyEnc1E0.html]ではAES暗号化アルゴリズムによる暗号化を説明しています。
Microsoftは、Crypto APIに替わる次世代暗号化(Cryptography Next Genaration; CNG)APIをサポートしています。このCNG APIの解説[http://www.trsustss.co.jp/cng/0000.html]もご参照ください。
C#(.NET Framework)では、CryptoAPIやCNGをラップしたクラスが提供されています。8.C#による簡単な暗号化で説明しています。
Microsoftは、Crypto APIに替わる次世代暗号化(Cryptography Next Genaration; CNG)APIをサポートしています。このCNG APIの解説[http://www.trsustss.co.jp/cng/0000.html]もご参照ください。
C#(.NET Framework)では、CryptoAPIやCNGをラップしたクラスが提供されています。8.C#による簡単な暗号化で説明しています。
1.1 概要
暗号の方式には、共通鍵による暗号方式(対称鍵暗号方式ともいう)と公開鍵による暗号方式(非対称鍵暗号方式ともいう)があります。ここでは、共通鍵による暗号化の方法を解説します。
共通鍵による暗号化では、暗号化するときに利用する鍵と、それを復号するときに利用する鍵に同じものを使います。ちなみに、公開鍵方式では、2つの違った鍵が使われます。
共通鍵による暗号化は、以下の関数を使います。
共通鍵による暗号化では、暗号化するときに利用する鍵と、それを復号するときに利用する鍵に同じものを使います。ちなみに、公開鍵方式では、2つの違った鍵が使われます。
共通鍵による暗号化は、以下の関数を使います。
- CryptAcquireContext
- CryptCreateHash
- CryptHashData
- CryptDeriveKey
- CryptEncrypt / CryptDecrypt
- CSPを準備する。
- ハッシュ計算の準備をする。
- パスワードからハッシュ値を算出する。
- 計算されたハッシュ値を鍵(暗号鍵)に変換する。
- 目的のデータを暗号化(または、復号)する。
1.2 キーコンテナー を準備する
CryptoAPIで暗号化・復号の処理をするには、CSP(CryptoGraphy Service Provider)という仕組みを利用します。
CSPは、暗号と復号およびハッシュ値の計算を行うソフトウェア・エンジンです。既定では、いくつかのCSPがインストール されていますので処理に合ったのCSPを選択します。CSPは、1つだけの機能ではなく、いくつかの機能(タイプといいます)を持ち合わせています。そのため、CSPの選択とともにどの機能を使うかといった指定(タイプの指定)も必要になります。
なお、CSPは、サードベンダーによって追加されることがあります。その場合は、 追加されたCSPの仕様にあわせて利用しなければなりません。
CSPの選択方法や、機能については、1.12 CSPについて を参照してください。
暗号化の場合は、CSP内部に持っている暗号化の鍵(キー)を使って処理が行われます。鍵(の種)は、外部から与えることもできます。このCSP内部の鍵が格納される場所をキーコンテナーといいます。
暗号化のエンジンは、ブラックボックス的に考えなければなりません。CSPという暗号化エンジンに鍵の種と暗号化されるデータを渡して、結果だけを受け取る。これがCSPを介した暗号化の処理です。
キーコンテナーを介して。どのCSPのどの機能を利用するかを指定します。
以下にコードを記します。
CSPは、暗号と復号およびハッシュ値の計算を行うソフトウェア・エンジンです。既定では、いくつかのCSPがインストール されていますので処理に合ったのCSPを選択します。CSPは、1つだけの機能ではなく、いくつかの機能(タイプといいます)を持ち合わせています。そのため、CSPの選択とともにどの機能を使うかといった指定(タイプの指定)も必要になります。
なお、CSPは、サードベンダーによって追加されることがあります。その場合は、 追加されたCSPの仕様にあわせて利用しなければなりません。
CSPの選択方法や、機能については、1.12 CSPについて を参照してください。
暗号化の場合は、CSP内部に持っている暗号化の鍵(キー)を使って処理が行われます。鍵(の種)は、外部から与えることもできます。このCSP内部の鍵が格納される場所をキーコンテナーといいます。
暗号化のエンジンは、ブラックボックス的に考えなければなりません。CSPという暗号化エンジンに鍵の種と暗号化されるデータを渡して、結果だけを受け取る。これがCSPを介した暗号化の処理です。
キーコンテナーを介して。どのCSPのどの機能を利用するかを指定します。
以下にコードを記します。
#include <WinCrypt.h> // キーコンテナーの取得 BOOL bResult; HCRYPTPROV hProv; bResult = CryptAcquireContext( &hProv, // ハンドルが戻ります NULL, // 既定のユーザーのCSPを使います MS_ENHANCED_PROV, // バンドルされたMicrosoftのCSPを指定 PROV_RSA_FULL, // タイプを指定 0); // キーコンテナーがある場合の指定です if(!bResult) { if(!CryptAcquireContext(&hProv,NULL,MS_ENHANCED_PROV,PROV_RSA_FULL, CRYPT_NEWKEYSET)) { fprintf(stderr, "CryptAcquireContext error\n"); return; } }
キーコンテナーがない場合は、新規に作成しなければなりません。はじめのCryptAcquireContext( )呼び出しでエラーとなった場合に新規に作成します。
エラーコード(NTE_BAD_KEYSET)を確認して正しく判断すべきですが、このままで十分に利用できます。正しい判断方法は、ヘルプ等で確認してください。
注意)
ここで紹介したキーコンテナーは、そのコンピューターのユーザーであれば誰でも利用できます。しかし、後に説明します方法で生成する鍵はセッション鍵といいワンタイム(一度だけ利用される)の鍵であり、このキーコンテナーに保存されません。その意味でセキュリティーの問題はありません。
注意)
ここで紹介したキーコンテナーは、そのコンピューターのユーザーであれば誰でも利用できます。しかし、後に説明します方法で生成する鍵はセッション鍵といいワンタイム(一度だけ利用される)の鍵であり、このキーコンテナーに保存されません。その意味でセキュリティーの問題はありません。
1.3 ハッシュ計算のインスタンスを生成する
CSPが内部に鍵を生成する場合は、ハッシュ値が使用されます。暗号化では、パスワードを利用する場合がよくあります。本ページでは、パスワードからハッシュ値を計算し、その結果を鍵の種として利用するようにします。
与えられたデータから計算されるハッシュ値は、常に同じ値になります。しかし、ハッシュ値からもとのデータを計算できません。これを利用して、復号の際にもパスワードからハッシュ値を計算し、その値を鍵の種として利用します。
暗号化の鍵は、必ず同じものが生成されなければなりません。たとえば、暗号化データを生成したコンピュータと、それを復号するコンピュータが違う場合は、それぞれで同じ鍵が生成されなければ、正しく復号できません。
以下で説明する方法は、同じ暗号化アルゴリズム、同じ鍵長などによって、暗号化と復号の環境が違っても復号できます。
まず、ハッシュ値を計算するインスタンスを生成します。ハッシュ値の計算は、CSPが処理しますので、そのハンドルを渡さなければなりません。
与えられたデータから計算されるハッシュ値は、常に同じ値になります。しかし、ハッシュ値からもとのデータを計算できません。これを利用して、復号の際にもパスワードからハッシュ値を計算し、その値を鍵の種として利用します。
暗号化の鍵は、必ず同じものが生成されなければなりません。たとえば、暗号化データを生成したコンピュータと、それを復号するコンピュータが違う場合は、それぞれで同じ鍵が生成されなければ、正しく復号できません。
以下で説明する方法は、同じ暗号化アルゴリズム、同じ鍵長などによって、暗号化と復号の環境が違っても復号できます。
まず、ハッシュ値を計算するインスタンスを生成します。ハッシュ値の計算は、CSPが処理しますので、そのハンドルを渡さなければなりません。
HCRYPTHASH hHash; bResult = CryptCreateHash( hProv, // ハッシュ値を計算するCSPのハンドル CALG_SHA, // ハッシュ値の計算アルゴリズム 0, // (後述します) 0, // 未使用、0(ゼロ)をセット &hHash); // 求めるインスタンスのハンドル if(!bResult) { fprintf(stderr, "CryptCreateHash error\n"); return; }
ハッシュ値の計算アルゴリズムは、以下の値が利用できます。
鍵つき(認証)ハッシュアルゴリズムは、ここでの暗号では使用しません。
第3引数には、鍵付きのハッシュを生成する際にキーのハンドルをセットしますが、ここでは使用しませんので、0(ゼロ)をセットします。
第4引数は、将来に予約された引数です。ここにも、0(ゼロ)をセットします。
第5引数にハッシュ計算のハンドルが戻されます。
CALG_HMAC 鍵つきハッシュアルゴリズム CALG_MAC メッセージ認証鍵つきハッシュアルゴリズム CALG_MD2 MD2ハッシュアルゴリズム CALG_MD4 MD4ハッシュアルゴリズム CALG_MD5 MD5ハッシュアルゴリズム CALG_SHA SHA-1ハッシュアルゴリズム CALG_SHA1 SHA-1ハッシュアルゴリズム(CALG_SHAと同じ) CALG_SSL3_SHAMD5 SSLクライアント認証 CALG_SHA_256 256ビットSHAハッシュアルゴリズム CALG_SHA_384 384ビットSHAハッシュアルゴリズム CALG_SHA_512 512ビットSHAハッシュアルゴリズム (ただし、CALG_SHA_256、CALG_SHA_384、CALG_SHA_512は Windows XP/200/NTでは利用できません。ここでは、MD5もしくは、SHA-1を選択するべきでしょう。鍵を生成するためのハッシュ計算ですのでアルゴリズムの選択には神経質になる必要はないでしょう。
鍵つき(認証)ハッシュアルゴリズムは、ここでの暗号では使用しません。
第3引数には、鍵付きのハッシュを生成する際にキーのハンドルをセットしますが、ここでは使用しませんので、0(ゼロ)をセットします。
第4引数は、将来に予約された引数です。ここにも、0(ゼロ)をセットします。
第5引数にハッシュ計算のハンドルが戻されます。
1.4 ハッシュ値を計算する
パスワードからハッシュ値を計算し、鍵の種を生成します。次のステップでこの値を使って暗号用の鍵を生成します。
ハッシュ値の計算は、以下のように行います。
ハッシュ値の計算は、以下のように行います。
#define PASSWORD "password" if(!CryptHashData( hHash, // ハッシュ計算インスタンスのハンドル (BYTE*)PASSWORD, // 実際のパスワードを指定 (DWORD)strlen(PASSWORD), // パスワードのバイト長 0)) // 後述します { fprintf(stderr, "CryptHashData error\n"); return 3; }
第2と第3引数には、実際のパスワードとそのバイト長を指定してください。
第4引数には、必要があればCRYPT_USERDATAフラグをセットできます。しかしながら、規定のCSPは、このフラグを無視します。このフラグがセットされると、CSPは(アプリケーションの介在なしに)直接ユーザーから値を入力するように求めます。
ちなみに、計算されたハッシュ値を取り出すには、CryptGetHashParam( )を使います。
第4引数には、必要があればCRYPT_USERDATAフラグをセットできます。しかしながら、規定のCSPは、このフラグを無視します。このフラグがセットされると、CSPは(アプリケーションの介在なしに)直接ユーザーから値を入力するように求めます。
ちなみに、計算されたハッシュ値を取り出すには、CryptGetHashParam( )を使います。
1.5 鍵の生成
ここで、暗号化と復号で使う「鍵」を生成します。先に説明しましたように、以下の関数で生成される鍵は、セッション鍵で一度だけ使われる鍵です。そのため、CSPに保存されませんので、セキュリティ上問題はありません。もちろん、アプリケーションは、パスワードや鍵が重要なものであるという認識を持ってアプリケーションを作成しなければなりません。
以下のようにします。
以下のようにします。
#define KEYLENGTH_128 0x0080 * 0x10000 // 128-bit長 HCRYPTKEY hKey; if(!CryptDeriveKey( hProv, // CSPのハンドル CALG_RC4, // 暗号化のアルゴリズム hHash, // ハッシュ値のハンドル KEYLENGTH_128, // 暗号化鍵のビット長とフラグ &hKey)) // 鍵のハンドル { fprintf(stderr, "CryptDeriveKey error\n"); return 4; }
第2引数には、共通鍵暗号のアルゴリズムを指定します。暗号化のアルゴリズムは、以下のとおり用意されています。しかし、CSPそれぞれで利用できるアルゴリズムが違いますので注意してください。詳細は、1.12 CSPについて[http://www.TrustSS.co.jp/smnEasyEnc1C0.html]を参照してください。
第3引数には、ハッシュ値のハンドルを指定します。この値から、暗号用の鍵を生成します。
第4引数には、生成されるべき鍵の長さ(ビット長)とフラグをセットします。詳細は、1.13 鍵の生成について [http://www.TrustSS.co.jp/smnEasyEnc1D0.html]を参照してください。
第5引数に、生成された鍵のハンドルが戻ります。
CALG_DES DES暗号化アルゴリズム CALG_DESX DESX暗号化アルゴリズム CALG_3DES トリプルDES暗号化アルゴリズム CALG_3DES_112 112ビット2キーDES暗号化アルゴリズム CALG_AES AES暗号化アルゴリズム CALG_AES_128 128ビットAES暗号化アルゴリズム CALG_AES_192 192ビットAES暗号化アルゴリズム CALG_AES_256 256ビットAES暗号化アルゴリズム CALS_RC2 RC2ブロック暗号化アルゴリズム CALG_RC4 RC4ストリーム暗号化アルゴリズム CALG_RC5 RC5ブロック暗号化アルゴリズム CALG_SEAL SEAL暗号化アルゴリズム CALG_SKIPJACK Skipjackブロック暗号化アルゴリズムまた、上記コードで指定しましたCALG_RC4は、ストリーム暗号のアルゴリズムです。これに対して、ブロック暗号(CALG_RC2、CALG_DESなど)が指定できます。詳細は、1.12 CSPについて [http://www.TrustSS.co.jp/smnEasyEnc1C0.html]を参照してください。なお、ブロック暗号化による暗号化の解説[http://www.trustss.co.jp/smnEasyEnc1E0.html]ではサンプルコードと共にその方法を説明しています。
第3引数には、ハッシュ値のハンドルを指定します。この値から、暗号用の鍵を生成します。
第4引数には、生成されるべき鍵の長さ(ビット長)とフラグをセットします。詳細は、1.13 鍵の生成について [http://www.TrustSS.co.jp/smnEasyEnc1D0.html]を参照してください。
第5引数に、生成された鍵のハンドルが戻ります。
1.6 暗号化
いよいよデータを暗号化します。
暗号化用の鍵を生成する際に、ストリーム暗号用の鍵を生成しました。この暗号方式では、暗号化する前のデータのバイト長と暗号化したデータのバイト長が同じになります。そのため、説明では簡単に暗号化データを取り出しています。入力のデータ長も短いために暗号化の関数を一度コールするだけで完成しています。実際に利用する場合には、データが大きくて1度の関数呼び出しだけでは不足する場合もあります。その場合は、ここで説明するCryptEncrypt( )関数の大3引数をfalseにして必要なだけ関数をコールします。そして最後の関数コールで第3引数をtrueにすれば暗号化できます。復号の場合も同じです。
ブロック暗号化の解説もあります、参照してください。
以下に、暗号化のコードを記します。
暗号化用の鍵を生成する際に、ストリーム暗号用の鍵を生成しました。この暗号方式では、暗号化する前のデータのバイト長と暗号化したデータのバイト長が同じになります。そのため、説明では簡単に暗号化データを取り出しています。入力のデータ長も短いために暗号化の関数を一度コールするだけで完成しています。実際に利用する場合には、データが大きくて1度の関数呼び出しだけでは不足する場合もあります。その場合は、ここで説明するCryptEncrypt( )関数の大3引数をfalseにして必要なだけ関数をコールします。そして最後の関数コールで第3引数をtrueにすれば暗号化できます。復号の場合も同じです。
ブロック暗号化の解説もあります、参照してください。
以下に、暗号化のコードを記します。
BYTE pbData[100] = "This is a test data."; DWORD dwDataLen = (DWORD)strlen((char*)pbData) + 1; if(!CryptEncrypt( hKey, // 鍵のハンドル 0, // 暗号化と同時にハッシュ化する場合に指定 TRUE, // 最後のデータを渡すときに真 0, // ゼロを指定 pbData, // 暗号化されるデータのポインター &dwDataLen, // 暗号化データのバイト長データの格納ポインター (DWORD)100)) // データバッファーの大きさ { fprintf(stderr, "CryptEncrypt error\n"); return 5; }
第2引数には、暗号化されるデータを暗号化と同時にハッシュ値にする場合に設定します。電子署名のアプリケーションなどで利用されます。今回のようなアプリケーションでは、その必要がありませんので、0(ゼロ)をセットします。
第3引数は、大量のデータを分割して関数に渡す場合に利用します。例えば、2回に分けて渡す場合は、最初のデータを渡すときにFALSEを指定し、2つ目のデータを渡すときにTRUEを指定します。
第4引数は、特殊な暗号化を行う場合に指定します。ここでは、0(ゼロ)を指定します。
第5引数には、暗号化するデータのポインターが入ります。戻りデータの大きさがわからないとき、で解説しますが、NULLが設定されてもエラーになりませんのでご注意ください。
第6引数には、データの長さが入ります。
第7引数には、暗号化データが格納されるバッファーのバイト長を指定します。
暗号化データは、pbDataに格納され、そのバイト長は、dwDataLenに格納されます。
第3引数は、大量のデータを分割して関数に渡す場合に利用します。例えば、2回に分けて渡す場合は、最初のデータを渡すときにFALSEを指定し、2つ目のデータを渡すときにTRUEを指定します。
第4引数は、特殊な暗号化を行う場合に指定します。ここでは、0(ゼロ)を指定します。
第5引数には、暗号化するデータのポインターが入ります。戻りデータの大きさがわからないとき、で解説しますが、NULLが設定されてもエラーになりませんのでご注意ください。
第6引数には、データの長さが入ります。
第7引数には、暗号化データが格納されるバッファーのバイト長を指定します。
暗号化データは、pbDataに格納され、そのバイト長は、dwDataLenに格納されます。
1.7 復号
次は、暗号化したデータを復号します。
復号の場合も、暗号化と同様に鍵のハンドルを用意します。この方法は、暗号化とまったく同じ手順で行います。以下では、同じ手順で求めたプロバイダーと鍵のハンドルを使って暗号化データを復号します。
復号の場合も、暗号化と同様に鍵のハンドルを用意します。この方法は、暗号化とまったく同じ手順で行います。以下では、同じ手順で求めたプロバイダーと鍵のハンドルを使って暗号化データを復号します。
if(!CryptDecrypt( hKey, // 鍵のハンドル 0, // 復号と同時にハッシュ計算をする場合に指定 TRUE, // 最後のデータを渡すときに真、他は偽 0, // 特殊な服後をする場合にフラグをセットする pbData, // [in]暗号化データ/[out]復号したデータ &dwDataLen)) // [in]暗号化データのバイト長 /[out]復号したデータのバイト長 { fprintf(stderr, "CryptDecrypt error\n"); return 6; } printf("データは、「%s」です。\n", pbData)
第2引数には、ハッシュ計算のインスタンスのハンドルを渡します。復号と同時にハッシュ値が計算されます。このハッシュ値をもとのハッシュ値と比較するような電子署名のアプリケーションで利用します。ここでは、その機能を利用しませんので0(ゼロ)を指定します。
第3引数は、大量のデータを渡す場合に利用します。データを分割して関数に渡す場合、残りのデータがある場合は、FALSEをセットし、残りのデータがなくなった最後のデータを渡すときにTRUEを渡します。
第4引数は、特殊な復号を行う場合にフラグをセットしますが、こんかいのアプリケーションでは使用しませんので、0(ゼロ)をセットします。
第5引数には、データのポインターを渡します。入力では、暗号化したデータが入ります。出力では、復号されたデータが入ります。戻りデータの大きさがわからないとき、で解説しますが、NULLが設定されてもエラーになりませんのでご注意ください。
第5引数には、データのバイト長をセットします。もし、復号されたデータが、暗号化されたデータと違う場合(ブロック暗号化の場合)は、この値が関数によって変更されますので第3引数のTRUEを渡した時に確認が必要です。
第3引数は、大量のデータを渡す場合に利用します。データを分割して関数に渡す場合、残りのデータがある場合は、FALSEをセットし、残りのデータがなくなった最後のデータを渡すときにTRUEを渡します。
第4引数は、特殊な復号を行う場合にフラグをセットしますが、こんかいのアプリケーションでは使用しませんので、0(ゼロ)をセットします。
第5引数には、データのポインターを渡します。入力では、暗号化したデータが入ります。出力では、復号されたデータが入ります。戻りデータの大きさがわからないとき、で解説しますが、NULLが設定されてもエラーになりませんのでご注意ください。
第5引数には、データのバイト長をセットします。もし、復号されたデータが、暗号化されたデータと違う場合(ブロック暗号化の場合)は、この値が関数によって変更されますので第3引数のTRUEを渡した時に確認が必要です。
1.8 リソースの開放
暗号化や復号の処理が終わりましたら、利用したリソースを開放します。
鍵オブジェクトやハッシュ計算のインスタンスハンドルは、以下の方法で開放します。
鍵オブジェクトやハッシュ計算のインスタンスハンドルは、以下の方法で開放します。
if(!CryptDestroyKey(hKey)) { fprintf(stderr, "CryptDestroyKey error\n"); return; } if(!CryptDestroyHash(hHash)) { fprintf(stderr, "CryptDestroyHash error\n"); return; }引数に、鍵オブジェクトやハッシュ計算のハンドルを指定します。
続いて CSP のハンドルを開放します。
if(!CryptReleaseContext(hProv, 0)) { fprintf(stderr, "CryptReleaseContext error\n"); return; }
第1引数に CSP のハンドルを指定します。
第2引数には、0(ゼロ)を指定します。
また、暗号化や復号処理のために確保した領域も開放するのを忘れないようにしてください。
第2引数には、0(ゼロ)を指定します。
また、暗号化や復号処理のために確保した領域も開放するのを忘れないようにしてください。
1.9 サンプル・コード
上記の解説にあるサンプルのコードです。Visual Studio .NET 2003用のプロジェクト・データもあります。
なお、次のページではこのサンプルを使って暗号化したデータをダウンロードできます。ご自身のWindowsコンピュータで復号できることを確認してください。暗号化したデータを暗号化したコンピュータと違うコンピュータで復号できるかとのご質問を多くいただいていますので確認できるようにしました。
サンプルコードの商業利用および転載を禁止します。
なお、次のページではこのサンプルを使って暗号化したデータをダウンロードできます。ご自身のWindowsコンピュータで復号できることを確認してください。暗号化したデータを暗号化したコンピュータと違うコンピュータで復号できるかとのご質問を多くいただいていますので確認できるようにしました。
サンプルコードの商業利用および転載を禁止します。
1.10 ご質問・ご要望
ご質問やご要望をお送りください。(匿名でも送信ください。ご質問やご要望内容は、公表しません。)
ご協力をお願いします、この記事は役に立ちましたか? | |
ご質問・ご要望 | |
メールアドレス | |
(記載の会社名および製品名は、各社の登録商標および商標です。)