フォレンジック屋さんなのに、何故か暗号ネタが続きます。
なんで暗号やるのかって?
もちろん、暗号の正しい実装ができていなければ、そこを突かれてしまうため、性質、制限、必須事項を知っておく必要はありますが。
一番の理由は、マルウェア解析で、ランサムウェアを解析したりするからですよ。
解析できると、当然どんな手法で暗号化しているかが分かるのですが。
ただ、「この実装問題ないの?」というものも結構あり、これサイドチャネル攻撃通るんじゃね?なんて思うこともあります。
ランサムウェアで暗号化されても、身代金払わず復元できるなら・・・なんて思って勉強してる次第です。
それにしても、ランサムウェアを解析していると、暗号プログラムの書き方憶えたり、暗号に詳しくなったり、大きな声では言えない知識が身に付いたりと、割と副作用が多くて面白いですよ!(最後のはいいのか・・・?)
暗号鍵と初期化ベクトル
今回は、ブロック暗号のCBCモードにおける暗号鍵と初期化ベクトルの違いについて、確認と検証をしてみた、という内容です。
この2つの違いは、本やインターネットで調べても、なかなかしっくりこなかったのです。
そのため、この際コードを書いて、検証してみました。
コードは「Windowsで暗号プログラムを書いてみる(AES編)」のコードで、関数に初期化ベクトルのポインタとサイズを引数に足しただけなので、その程度は簡単に改造できるだろうということでコードの掲載は省略します。
万一、記事見て「ホントかよ!」と思って自身で検証しようなんて思う酔狂な方は、ちょっと改造してみてください。
Pythonで作ってもいいかもしれませんね。PythonにAES暗号のライブラリあるかどうか知らんけど。
さて、暗号鍵と初期化ベクトルは何が違うんでしょう?
というか、暗号鍵はみんな分かるんです。
ブロック暗号やストリーム暗号では、これで暗号化した場合、復号するときにも同じ鍵がなければ復号できません。
これは説明されるまでもなく分かるんです。多分みんなそう。
では、初期化ベクトルってなんぞ?という話です。
さっきのページのサンプルでは、初期化ベクトルは特に設定せず、オール0にしてしまっていますね。
これでも、暗号鍵があれば暗号化はできるし、暗号鍵が無ければ復号できないし、暗号鍵を変えれば暗号文も変わります。
初期化ベクトルの説明を見ると、「同じ暗号鍵を使って同じ平文を暗号化する時に、初期化ベクトルを変えることで暗号文を異なるものにする」というような説明がされています。
これは、同じ平文を同じ暗号鍵で暗号化すると、暗号文が同じものになり、反射攻撃等に利用できることに対する対策ということだと思います。
例えば、暗号を通信で使うとして、「何か起きた」ときに報告として毎回同じデータを送っていれば、その暗号文を平文に解読できなくても、「何か起きた」(だからこのデータが通信で飛んでいる)ということが分かってしまう、ということですね。
私のような凡人は、「同じ平文を同じ暗号鍵で暗号化すると、同じデータになるに決まってるじゃん!」と思うわけですが、そうならないように作られているのが現代の暗号といえます。
それでも暗号文を変えたいなら、鍵変えればいいじゃないかとも思いますが、都度鍵交換するのも大変ではあります。
そこで、初期化ベクトルという初期値を入れることによって、フィードバックしていくデータに波及させ、「同じ平文を同じ暗号鍵で暗号化しても違うデータになる」というようにしたわけです。
しかし、「それなら、結局その初期化パラメータを暗号鍵のように秘匿して送る、または暗号鍵同様に安全に保持しないといけないのではないか」と考えてしまいました。
これが、私の浅はかな思考だった、ということを実証して証明するのが今回の記事です。なんだこのマゾい記事は。
初期化ベクトルの値が違う場合の暗号化結果の違い
では、実際に同じ暗号鍵で初期化ベクトルが違う場合の暗号化をしてみます。
平文として、以下のデータを使用します。
なお、文面に反論は認めない!
(注:画像が間違っていたので貼り直しました。すいません。)
そして、暗号鍵は以下を使います。
AD 35 F9 B1 D3 78 02 88 C1 9A E5 7D 80 23 B8 54
初期化ベクトルは以下の2つをそれぞれを使います。
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
これらをパラメータとして、AES暗号のCBCモードで暗号化した結果は、それぞれ以下の通りとなります。
初期化ベクトル:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
初期化ベクトル:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
同じ暗号鍵でも、初期化ベクトルが異なると全く異なる暗号化結果となりました。
まあ、当然なんですが、私が素人なので一つ一つ確認していきます。
暗号化と同じ鍵と初期化ベクトルで復号
一応、復号ができることも確認します。
「鍵が同じなのに暗号化結果が異なったら、復号できなくなる」じゃ困りますからね。
復号側が暗号鍵、初期化ベクトルを安全な方法で受け取っていたとして、暗号化データを復号してみます。
鍵は暗号化と同じもの、初期化ベクトルはそれぞれ暗号化時と同じものを与えて復号します。
初期化ベクトル:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
初期化ベクトル:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
当然といえば当然なのですが、それぞれ元のデータに戻りました。
逆にいえば、復号側は、やっぱり暗号鍵と初期化ベクトルが分からないと、ちゃんと復号できないじゃないか、と私は思ったわけです。
と、いうことは、「初期化ベクトルが分からないと復号化できないのだから暗号鍵と同じ意味を持つ」。
・・・と思うじゃん?
私も、最初はこう思っていました。
しかし、こう理解してしまうと、実は暗号の実装において致命的な失敗をしかねないのです。
そう、実際は、やはり暗号鍵と初期化ベクトルの意味は違い、初期化ベクトルはあくまで「同じ平文を同じ暗号鍵で暗号化した際に、暗号化結果が違うようにする」ためだけのものなのです。
そのための実験をしてみました。
暗号化と同じ鍵だが異なる初期化ベクトルで復号
では、暗号鍵が同じだが、異なる初期化ベクトルで復号化したらどうなるのでしょう?
同じ平文、同じ暗号鍵で初期化ベクトルを変えたら暗号化結果が変わったのだから、初期化ベクトルを合わせないと復号だってうまくいかない、そう思いますね。
同じ平文を、以下の条件で暗号化した結果を用意します。
先に出していた暗号化結果を示します。
暗号鍵:AD 35 F9 B1 D3 78 02 88 C1 9A E5 7D 80 23 B8 54
初期化ベクトル:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
さて、これを以下の条件で復号化してみます。
暗号鍵:AD 35 F9 B1 D3 78 02 88 C1 9A E5 7D 80 23 B8 54
初期化ベクトル:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
・・・初期化ベクトルオール0で復号化に成功したデータは、先の「暗号化結果」で掲載したとおりなので、上手く復号ができるはずがないですね。
それでは、復号結果を見てみましょう。以下のようになりました。
暗号化時初期化ベクトル:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
復号化時初期化ベクトル:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
「Yuyuko is the most beautiful in the world.幽々子は俺様の嫁。」の部分は復号化できている!?
これは、宇宙の真理は初期化ベクトルが違っても復号化できてしまうということか!!
・・・んなわけねーだろwww
これが、ブロック暗号におけるCBCモードの特性なのです。
どうしてこうなった?
どうしてこんな結果になってしまったのでしょう?
これを考察するには、暗号化モードでパラメータがどのように波及するか、ということをちゃんと理解しておかなければいけない、ということです。
さらにいえば、暗号化モードの特性をよく理解しておく必要がある、とも言えます。
暗号化モードについては、「Windowsで暗号プログラムを書いてみる(AES編の予備知識)」で参考のリンクを付けています。
図示されていると思いますので、CBCモードを見てください。
暗号化する際、最初のブロックは初期化ベクトルとXORした結果を暗号化しています。
そして、第2ブロックは第1ブロックの暗号化結果を用いてXORした結果を暗号化しています。
一方、復号化する際、最初のブロックは復号化した結果と初期化ベクトルをXORすることで平文を得られます。
そして、第2ブロックは復号化した結果と第1ブロックの暗号化データを用いてXORすることで平文を得られます。
つまり、第2ブロックの暗号化や復号化では、暗号化された結果の第1ブロックのデータさえ分かればよく、初期化ベクトルが分からなくても復号できるということを示しているのです。
この結果、仮に暗号文を攻撃しようとした場合、AES暗号のCBCモードでは以下のようになると思います(暗号詳しい人、間違ってたらツッコんでクレメンス)。
初期化ベクトル既知、暗号鍵未知の場合:
暗号鍵を総当たり攻撃しないと解けない。
初期化ベクトル未知、暗号鍵既知の場合:
17バイト目以降のデータは復号化可能
初期化ベクトルを秘匿しても、守れるのは最初のブロック(16バイト)だけで、それ以降は秘匿の効果はありません。
そのため、秘匿の重要度は、
暗号鍵 > 初期化ベクトル
となります。
また、暗号通信等の実装でAES暗号CBCモードを使う場合、最初の16バイトにダミーデータを入れてしまって、暗号化側は任意の初期化ベクトルを使用して暗号化し、復号側はオール0の初期化ベクトルを使って復号し、最初のダミーデータを捨てるようにすれば、初期化ベクトルの交換自体が不要になります。
今回の実験でも、このためにあえて第1ブロックの16バイトにダミーの文字列を入れていたわけです。(珍しくちゃんと考えていた)
この場合でも、同じ平文を同じ暗号鍵で暗号化しても、初期化ベクトルを毎回変えることで暗号文が変わる、というメリットが残ります。
こういった特性を理解することは、暗号のより効果的な利用のアイディアを出したり、脆弱性を作りこまないようにするうえで、とても重要になるのではないかと思います。
感 想
暗号化する必要があるとか、ブロック暗号ならAES暗号が安全とか、暗号化モードはこれがいいとか、セキュリティ屋さんとしてエラそうに言うこともあるわけですが。
どういう暗号があり、どういうメリットがあって、どういう設定をするという「知識」は、確かにセキュリティの本を読めば知ることができます。
しかし、それはあくまで「最近のトレンドにおけるテンプレを憶えてるだけ」になってしまっていて、十分な「理解」はできていなかった、ということです。
こういうのを理解して使いこなしているハッカーと呼ばれる人々には、とてもじゃないですが歯が立ちそうにありませんね。
さて、暗号化モードは他にもあるわけですが。
今、色々調べていても、まだちゃんとした答えが見つからないのが、CTRモードの時のNonceについて。
CTRモードは、Nonceとカウンターの値の和を暗号化し、それを乱数とみなしてXORする暗号化方式と理解しています。
このモードの暗号化の方法から考えると、CBCモードの初期化ベクトルと違い、復号側も知っていないと上手く復号できないと思います。
では、このNonceは、攻撃者に知られても暗号の強度は下がらないか?
Nonceが既知または固定の場合、暗号強度が下がって復号化される恐れは無いのか?
といったことが、イマイチよくわかりません。
なんでこんなことを調べているかというと、冒頭で述べた通り、ランサムウェアを解析することがあるからです。
ランサムウェアって、実行し始めたら、C&Cとの通信部分を除くと、わりとそれ単体で最後まで動いてくれたりします。
このため、解析の練度維持向上に割と便利だったりするんですが。
ランサムウェアの暗号の実装を見ていると、「これって本当に問題ないのか?」というものを目にします。
RSA暗号を使っているといっても、被害端末の中で秘密鍵と公開鍵のペア作ってたり、AES暗号だってECBモードだったり。
CBCモードは使っていても、初期化ベクトルをちゃんと入れてるランサムウェアなんて見たことありません。
そんな中で、「Nonceが0固定のCTRモードを自力で実装しているような?」ランサムウェアがあったりしました。
その中で、「これってサイドチャネル攻撃で復号化できるんじゃね?」という点も見つけました。
CTRモードって、乱数生成をAES暗号で疑似的にやってるバーナム暗号みたいな仕組みだと理解しているんですが、そもそもバーナム暗号の乱数って自然乱数かそれに近いものである必要があり、あのように再計算できる疑似的乱数を使って、果たして強度維持できるのか?というところから疑問だったりします。
まあ、エラソーなことは、それ破ってから言えってことになりますが。
「ランサムウェアを解析し、復号化ツール作りました(キリッ)」と言えるようになるには、まだまだ勉強不足、といったところです。
いやはや、今回は自分の勉強不足を露呈し、自ら再確認するという、本当にマゾい記事でした。
艦〇れのミニ期間限定海域イベントでもやって寝るか。