暗号化 API(Cryptography API) のよくある問題、勘違い!?

こんにちは小泉です。
雪は降るし、まだ風も冷たいし、なかなか春の音が聞こえてきませんね。
ちょっと寒くて乗らなかったら、そろそろ私のバイクもバッテリの電圧が少なくなってきたようです。たま~に土日に乗ろうとすると、エンジンのかかりが悪く、始動のたびにキュルキュルとご近所迷惑な音を立てる始末。。。。。ご近所のみなさま申し訳ありません。あの音は私です。

さて、今までは私は Network MonitorTcpAnalyzerWinSock API といったネットワーク関連の投稿ばかりでしたが、今回は趣向を変えて暗号化 API に関するお話をさせていただきます。
ネットワーク上に流れるデータや、ローカルに保存したデータを安全に取り扱いたいと思ったとき、まず思い浮かぶのはデータの暗号化ではないでしょうか。そんな時に弊社が長年提供している暗号化 API である Cryptography API をご利用されている方も多いかと思います。サポートをしていると、かなりの割合で、初期化関数である CryptAcquireContext API の利用方法に起因したトラブルが多いように感じます。そこで今回は、CryptAcquireContext API をご利用いただく上で、よくある誤解について記載させていただきますね。Cryptography API 初心者の方は今後の開発の際の参考に、経験者の方はトラブルシューティングの際の参考にしていただけますと幸いです。

誤解その 1.
-----------------------------------------------------------------------------------------------------------------
CRYPT_NEWKEYSET フラグは絶対必要ですか?

回答:
共通鍵暗号 (3DES、AES、RC5 等)やハッシユ(SHA1, SHA2)を用いる場合は、必要はありません。
CryptAcquireContext API を利用するときに、よく見るのが以下のような呼び出しです。

                // Attempt to acquire a handle to the default key container.
                if(!CryptAcquireContext(&hProv, "Test Container", NULL, PROV_RSA_FULL, 0))
                {
                                if(GetLastError() != NTE_BAD_KEYSET) {

                                                // Some sort of error occured.
                                                printf("Error opening default key container! %d\n",GetLastError());
                                                goto Error;
                                } 

                                // Create default key container.
                                if(!CryptAcquireContext(&hProv,"Test Container", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
                                                printf("Error creating default key container! %d\n",GetLastError());
                                                goto Error;
                                }
                }

もし貴社のプログラムで共通鍵暗号やハッシユのみしか使わないようであれば、最後の dwFlags パラメーターに CRYPT_NEWKEYSET を指定しても、実は意味はありません。なぜなら共通鍵暗号やハッシユは Key Container (厳密には Key Container ファイル)に鍵を保存しないからです。共通鍵暗号やハッシユをご利用の際は、CRYPT_VERIFYCONTEXT をご指定ください。ちなみに dwFlags パラメーターに CRYPT_VERIFYCONTEXT を指定する場合は、上記の例は以下の数行の呼び出しで代替できるので、コードもかなりスッキリしますね。

       // Attempt to acquire a handle to the default key container.
        if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
                   printf("Error creating default key container! %d\n",GetLastError());
                   goto Error;
       }

 

誤解その 2.
-----------------------------------------------------------------------------------------------------------------
デフォルトの Key Container は利用していい?

回答:
非対称鍵暗号(RSA, DSS 等)をご利用の際は避けてください。
これまた CryptAcquireContext API ですが、pszContainer パラメーターに NULL を指定された場合、デフォルトの Key Container が利用されます。そして、上記で説明した CRYPT_NEWKEYSET と組み合わせて利用すると、以降プログラムで作成した非対称鍵はそのユーザアカント単位で作成される Key Container ファイルに保存されます。これは、仮に第三者のプログラム内で同じユーザアカウント上で、同じように CryptAcquireContext API を呼び出した場合、簡単にデフォルトの Key Container をプログラム間で共有できるという事を示します。(まぁ、元々 Key Container は、鍵を共有するための仕組みなので、当たり前ですが。。。。。) ただ、この結果、何がおこるかと言うと、知らない間に第三者のプログラムに鍵を盗み見られたり、書き換えられたり、壊されたりというセキュリティリスクが発生してしまいます。サポートをやっていると意外とこの問題が多いですね。このため、非対称鍵暗号(RSA 等)と CRYPT_NEWKEYSET と組み合わせて利用する際は、以下の例のように pszContainer には必ず貴社独自の Key Container 名を指定し、独自の Key Container をご利用ください。

           // Attempt to acquire a handle to the default key container.
           if(!CryptAcquireContext(&hProv, "Test Container", NULL, PROV_RSA_FULL, 0))
                {
                                if(GetLastError() != NTE_BAD_KEYSET) {
                                                // Some sort of error occured.
                                                printf("Error opening default key container! %d\n",GetLastError());
                                                goto Error;
                                }

                                // Create default key container.

                                if(!CryptAcquireContext(&hProv,"Test Container", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {

                                                printf("Error creating default key container! %d\n",GetLastError());

                                                goto Error;

                                }

                }

 

誤解その3. -----------------------------------------------------------------------------------------------------------------
アプリケーション プログラムで正常に動作していたのに、サービスプログラムに移植したら CryptAcquireContext API で失敗する。サービス プログラムでは Cryptography API は利用できない?

回答:
Cryptography API は、サービス プログラムでも問題なく利用できますので、ご安心ください。
これまた CryptAcquireContext API の dwFlags パラメーターとなります。dwFlags パラメーターに CRYPT_NEWKEYSET を指定した場合、Key Container ファイルはユーザー プロファイルに保存されます。このため、ユーザー プロファイルがロードできないサービス プログラムでは、Key Container ファイルが作成できないため、ERROR_FILE_NOT_FOUND エラー失敗します。もし、サービスプログラムに移植したら動作しなくなったという現象が見られたら、以下の例のように dwFlags パラメーターに CRYPT_MACHINE_KEYSET を追加指定してみてください。CRYPT_MACHINE_KEYSET を指定する事で、ユーザー プロファイルではなくコンピューター共通のストアとして保存されるので、動作するはずです。

                // Attempt to acquire a handle to the default key container.
                if(!CryptAcquireContext(&hProv, "Test Container", NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
                {
                                if(GetLastError() != NTE_BAD_KEYSET) {
                                                // Some sort of error occured.
                                                printf("Error opening default key container! %d\n",GetLastError());
                                                goto Error;
                                } 

                                // Create default key container.
                                if(!CryptAcquireContext(&hProv,"Test Container", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET|CRYPT_MACHINE_KEYSET)) {
                                                printf("Error creating default key container! %d\n",GetLastError());
                                                goto Error;
                                }
                }

 

誤解その4. -----------------------------------------------------------------------------------------------------------------
昨日まで、正常動作していたのに急に動かなくなった!!
OS の修正モジュールを適用したせい?

回答:
もちろん OS の修正モジュールの可能性も否定できませんが、特定のユーザアカウントだけに発生する現象でしたら、そのユーザーに「パスワードを忘れため、システム管理者に自身のパスワードをリセットしてもらった事はありませんか?」と優しく聞いてみてください。
もし、貴社のプログラムで CryptAcquireContext API の dwFlags パラメーターに CRYPT_NEWKEYSET を指定し Key Container ファイル作成しており、且つ NTE_BAD_KEY_STATE や NTE_EXISTS エラーが返されているようでしたら、ほぼパスワードをリセットされた事に起因する問題となります。Key Container ファイルに鍵の情報が格納された場合は、鍵を安全に保管するためユーザーの ID やパスワードを元に鍵データを暗号化しています。通常のパスワード変更の場合だと、一旦古いパスワードを元にコンテナファイル内の情報を復号化し、新しいパスワードで暗号化し直すという処理を内部で行うため問題はありません。しかしながら、パスワードを強制変更(リセット)された場合は、単純にパスワードが強制的に上書きされる処理となります。このため、既存の暗号化済みのデータが復号化できなくなり、その Key Container ファイルには二度とアクセスできなくなります。これが原因です。技術情報「パスワードをリセットする際のリスクとは」にも記載のとおり、弊社としても単純なパスワードのリセットはあまりお勧めできる方法ではありません。次回よりパスワード リセット ディスクを準備いただくようユーザー様にお伝えください。

とはいえ、リセットしてしまったものは致しかたありません。
残念ながら鍵はあきらめていただく必要がありますが、以下の手順で復号化できなくなった Key Container ファイルを削除いただき、プログラムが動作するかお試しください。

<削除方法>
1)"%APPDATA%\Microsoft\Crypto\RSA\"以下にある”S-“で始まるフォルダーを開きます。
”S-“以降の文字列は、問題の発生しているユーザー毎に変わりますのでご注意ください。
(DSS を利用の場合は、"%APPDATA%\Microsoft\Crypto\DSS\" となります。)

2) 上記のフォルダー内の Key Container ファイルをノート パッド等のエディターで開き、貴社にて CryptAcquireContext
API の pszContainer パラメーターに指定した文字列が記載された Key Container ファイルを探します。
* Key Container ファイルの名前は環境毎に異なりますが、
"6da774fef392bc623eb146006db1f2bd_7dedfba3-9424-4bf1-b31d-3245d8d41c37"
のような 16 進数の文字列で保存されております。
*pszContainer パラメーターに NULL を指定されている場合は、ユーザー名が記載された Key Container ファイルを探します。

3) Key Container ファイルを特定したら該当ファイルを削除します。

 

以上となりますが、お役に立つ情報はありましたでしょうか。
機会があれば、また暗号化のお話もさせていただきたいと思いますので、お楽しみに!!

 

< 参考技術情報 >
CryptAcquireContext API の詳細は以下をご参照ください。
CryptAcquireContext function

以下でも詳細をご案内しておりますので、併せてご参照ください。
CryptAcquireContext()の使用とトラブルシューティング