libsodium:公開鍵認証暗号化、または秘密鍵なしでメッセージを復号化する方法

この記事は、暗号プリミティブの作業に対する誤解と過度の自信が、暗号保護の実装で重大なエラーにつながる可能性がある例を示しています。 私の間違いが誰かの役に立つ例になることを願っています。



それはすべて、1つの小さなプロジェクトで暗号化を使用する必要があるという事実から始まりました。 クライアントは、傍受から保護されたメッセージをサーバーに送信する必要があります。 クライアント認証は原則として必要ではなく、データは一方向(クライアントからサーバー)にしか送られず、さらに、共通の暗号化キーの保存に煩わされたくなかったため、非対称暗号化を使用することを考え出しました。 アイデアは単純に見えます。クライアントにはサーバーの公開鍵が与えられ、サーバーに送信されるメッセージを暗号化します。 サーバー(および彼のみ)は、受信したメッセージを、自分が知っている秘密キーを使用して解読できます。



暗号化プリミティブを手動で実装することは恩恵がなく、エラーに満ちているため、上記のアイデアを実装するためにいくつかのオープンソースライブラリを使用することが決定されました。 このプロジェクトでは、すでにZeroMQライブラリとそのCZMQラッパーを使用していたため、 libsodiumライブラリに基づいたデータ転送のセキュリティが確保されたため 、彼女が選択しました。 確かに、すべてがすでに含まれているのに、なぜ依存関係を作成します。



libsodiumについて
公式Webサイトで述べたように、libsodiumは暗号化、電子デジタル署名、ハッシュなどのためのオープンでモダンなシンプルなライブラリです。

libsodiumを使用するプロジェクトや企業の印象的なリストもあります。たとえば、 Tox



そのため、ドキュメントをすばやく読むと、ライブラリには公開鍵認証暗号化の楕円曲線上の非対称暗号化の実装が含まれていることがわかりました。 さらに、 MACを介してメッセージを認証することもできます。 MACの暗号化と生成はcrypto_box_easy



関数を使用して実行され、逆の手順(検証と復号化)はcrypto_box_open_easy



を使用してcrypto_box_open_easy



ます。



ドキュメントから:

オリジナル
公開鍵認証暗号化を使用して、ボブはアリスの公開鍵を使用して、アリス専用の機密メッセージを暗号化できます。

ボリスの公開鍵を使用して、アリスは暗号化されたメッセージが実際にボブによって作成されたものであり、最終的に解読する前に改ざんされていないことを確認できます。

アリスが必要とするのは、ボブの公開鍵、ナンス、および暗号文だけです。 ボブは、アリスとさえ、彼の秘密鍵を決して共有すべきではありません。

そして、アリスにメッセージを送信するために、ボブはアリスの公開鍵のみを必要とします。 アリスは、ボブとさえ、彼女の秘密鍵を決して共有すべきではありません。



認証サポート付きの公開キー暗号化を使用すると、Bobは公開キーを使用してAliceの機密メッセージを暗号化できます。



ボブの公開鍵を使用して、アリスは暗号化されたメッセージが実際にボブによって作成されたものであり、偽造されていないことを復号前に確認できます。



アリスに必要なのは、ボブの公開鍵、ナンス、および暗号化されたメッセージだけです。 ボブは、アリスからも秘密鍵を秘密にしなければなりません。



アリスにメッセージを送信するには、ボブはアリスの公開鍵のみが必要です。 アリスは、ボブからも秘密鍵を秘密にしなければなりません。


すべてがシンプルで明確なようです。 クライアントはサーバーの公開鍵でメッセージを暗号化し、秘密鍵でメッセージに署名します。 メッセージを受信したサーバーは、クライアントの公開キーを使用してチェックし、秘密キーで解読します。 サーバーは別として、公開鍵で暗号化されているため、メッセージを解読することはできません(少なくともこれは非対称暗号化の主要な原則です)。 しかし、悪魔は詳細にあります。



概念をテストするために、公式サイトから例をコピーしましたが、誤ってミスをして、奇妙な結果を得ました。



テストコード
 #include <string.h> #include "sodium.h" #define MESSAGE "test" #define MESSAGE_LEN 4 #define CIPHERTEXT_LEN (crypto_box_MACBYTES + MESSAGE_LEN) static bool TestSodium() { unsigned char alice_publickey[crypto_box_PUBLICKEYBYTES]; unsigned char alice_secretkey[crypto_box_SECRETKEYBYTES]; crypto_box_keypair(alice_publickey, alice_secretkey); unsigned char bob_publickey[crypto_box_PUBLICKEYBYTES]; unsigned char bob_secretkey[crypto_box_SECRETKEYBYTES]; crypto_box_keypair(bob_publickey, bob_secretkey); unsigned char nonce[crypto_box_NONCEBYTES]; unsigned char ciphertext[CIPHERTEXT_LEN]; randombytes_buf(nonce, sizeof nonce); // message alice -> bob if (crypto_box_easy(ciphertext, (const unsigned char*)MESSAGE, MESSAGE_LEN, nonce, bob_publickey, alice_secretkey) != 0) { return false; } unsigned char decrypted[MESSAGE_LEN + 1]; decrypted[MESSAGE_LEN] = 0; //  //if (crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce, alice_publickey, bob_secretkey) != 0) //   "" if (crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce, bob_publickey, alice_secretkey) != 0) { return false; } if(strcmp((const char*)decrypted, MESSAGE) != 0) return false; return true; }
      
      







アリスとボブのテストでは、最初にキーのペア( crypto_box_keypair



)がランダムに生成され、次にノンス( randombytes_buf



)がランダムに満たされます。 その後、アリスは公開キーを使用してボブへのメッセージを暗号化し、秘密キーを使用してMACを形成します。



 // message alice -> bob if (crypto_box_easy(ciphertext, (const unsigned char*)MESSAGE, MESSAGE_LEN, nonce, bob_publickey, alice_secretkey) != 0) { return false; }
      
      





ただし、復号化手順で、ミスをして間違ったパラメーターを渡しました。 Bobのメッセージをプライベートキーで解読する代わりに、BobのパブリックキーとAliceのプライベートキーでメッセージを解読しようとしました( コピーアンドペーストするように )。



 //   "" if (crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce, bob_publickey, alice_secretkey) != 0) { return false; }
      
      





メッセージが解読されたときの驚きを想像してください! それは非常に奇妙で、私を認知的不協和音の状態にしました。 私の目が発見される前に、0日間の脆弱性と世界社会の認識 。 彼の秘密鍵を使用せずにボブのメッセージを復号化する方法を理解できませんでした。 さらに、アリスの公開鍵を使用せずにMACチェックが正常に実行されました!



私が最初に考えたのは、元の例のように復号化することであり、すべてが問題なく行われました-メッセージは復号化され、検証されました。 したがって、復号化(および確認)するには、メッセージは任意のキーペア(ボブの公開キーとアリスの秘密キー、またはその逆)になります。ボブのプライベートキーとアリスの公開キーです。



2番目の考えは、古いバージョンのライブラリを使用していたことです。 最新バージョンに更新されましたが、テストの動作は変更されていません。



率直に言って、私はlibsodiumソースを掘り下げる時間と欲求がほとんどありませんでした。 答えはStackoverflowで見つかりました。 libsodiumは、「公開鍵認証暗号化」を、私に思われたものとは多少異なる方法で理解していません。



詳細なレビューの後、暗号化アルゴリズムは次のようになりました。



  1. ECDHアルゴリズムを使用して、対称暗号の共通キーが生成されます。
  2. メッセージは、最初のステップで取得した共有キーを使用して、 XSalsa20対称暗号を使用して暗号化されます。
  3. MAC シミュレーターPoly1305 )は、同じ共有キーを使用して生成されます。


このアルゴリズムの次の結論と特性は、これに基づいています。





アルゴリズムのこれらの特性は正常であり、事実は間違ったアルゴリズムを適用しようとしたことです。 ただし、公式Webサイトのドキュメントは、作品の機能やその使用に適した状況についてユーザーを誤解させると考えています。



私の目標は、秘密鍵を知らずにサーバーのメッセージを復号化できないアルゴリズムを使用することであったため、この暗号プリミティブのこの動作は私に合わず、使用を拒否しました。



私は考えを取り除くことができません:誰かがこの方法を使用して、実績のあるライブラリの信頼性を期待して真に重要なシステムを実装した場合、別のミスを認識し、関数の「奇妙な」動作に偶然つまずいたため。



私は誰かに役立っていたと思います。 皆さん、頑張ってください。



All Articles