具体的なユースケースは、アルゴリズムを理解するのに非常に役立つことがわかりました。これが2つのユースケースとステップバイステップのウォークスルーです。
両方のユースケースの開始点。
これらのユースケースは、メッセージを復号化するときに、CBCチェーンモードとブロック量子化のための暗号文スティーリングを備えたAES-256を使用することを前提としています。これらのユースケースを生成するために、Delphi2010コンパイラとTurboPowerLockBox3ライブラリ(SVNリビジョン243)を使用しました。以下では、そのような表記を使用します...
IV := [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
...「IV」という名前の変数が16バイトの配列に等しくなるように割り当てられていることを意味します。左端のバイトは、配列の最下位バイト(最下位アドレス)のレンダリングであり、右端のバイトは最上位バイトです。これらのバイトは16進数で書き込まれるため、たとえば...
X := [2] 03 10
... LSBが3、MSBが16であることを意味します。
ユースケース1
AES-256 32バイト圧縮キー(AES標準で定義されている)を...
key = [32] 0D EE 8F 9F 8B 0B D4 A1 17 59 FA 05 FA 2B 65 4F 23 00 29 26 0D EE 8F 9F 8B 0B D4 A1 17 59 FA 05
TurboPower LockBox 3では、これはTCodecコンポーネントのパスワード('UTF8Password')プロパティを...に設定することで実現できます。
password = (UTF-8) 'Your lips are smoother than vasoline.'
送信されるプレーンテキストメッセージは次のようになります
Message = (UTF-8) 'Leeeeeeeeeroy Jenkins!'
エンコードされたこれは22バイトの長さです。AES-256のブロックサイズは16バイトであるため、これは1〜2ブロックの長さです。
IVを1とします。(余談ですが、Delphi側では、これは次のように設定することで実現できます。
TRandomStream.Instance.Seed := 1;
暗号化の直前)。したがって、PHPによって復号化される暗号文メッセージは次のようになります(8バイトのIVがLockBox3の前に付加されます)...
ciphertext = [30] 01 00 00 00 00 00 00 00 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 1D 66 DB 97 2E 2C
(base64 equivalent ='AQAAAAAAAAAXXMCX/+9jWoiDbABiv4flHWbbly4s')
これをIV、最初の暗号文ブロック(c [0])、最後の(部分的な)暗号文ブロック(c [1])に分解します。
IV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
c[0] = [16] 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5
c[1] = [6] 1D 66 DB 97 2E 2C
それでは、暗号文を盗むことによる復号化について見ていきましょう。
CV:= IV
CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
一般に、n番目のブロック(最後の2ブロックを除く)の場合、通常のCBCアルゴリズムは...
m[n] := Decrypt( c[n]) XOR CV;
CV[n+1] := c[n]
どこ:
- mは出力平文ブロックです。
- Decrypt()は、そのブロックでのAES-256ECB復号化を意味します。
- CVは私たちのキャリーベクターです。連鎖モードは、これがブロックごとにどのように変化するかを定義します。
ただし、最後から2番目のブロック(N-1)(ユースケース1ではN = 2)の場合、変換は...に変更されます(この例外は、暗号文の盗用が選択されているために発生します)
m[n] := Decrypt( c[n]) XOR CV;
CV[n+1] := CV[n] // Unchanged!
ユースケースへの適用:
CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
c[0] = [16] 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5
Decrypt(c[0]) = [16] 6F 6B 69 6E 73 21 F0 7B 79 F2 AF 27 B1 52 D6 0B
m[0] := Decrypt(c[0]) XOR CV = [16] 6E 6B 69 6E 73 21 F0 7B 79 F2 AF 27 B1 52 D6 0B
最後のブロックを処理します。これは部分的なもので、長さは6バイトです。一般的に、最後のブロックの処理は次のようになります...
y := c[N-1] | LastBytes( m[N-2], BlockSize-Length(c[N-1]));
m[N-1] := Decrypt( y) XOR CV
ユースケース1への適用:
c[1] = [6] 1D 66 DB 97 2E 2C
y := c[1] | LastBytes( m[0], 10)
y = [16] 1D 66 DB 97 2E 2C F0 7B 79 F2 AF 27 B1 52 D6 0B
Decrypt( y) = [16]= 4D 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65
m[1] := Decrypt(y) XOR CV
m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65
復号化プロセスの最後のステップは、最後の2つのブロックの放出です。順序を逆にして、最初にm [N-1]を放出し、次にm [N-2]の最初の部分(長さはc [N-1]の長さに等しい)を放出します。ユースケース1に適用しています...
m[1]を出力します
m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65
m[0]の最初の6バイトを出力します
FirstBytes( m[0], 6) = 6E 6B 69 6E 73 21
まとめると、...の再構成された平文が得られます。
[22] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65 6E 6B 69 6E 73 21
これは、「LeeeeeeeeeroyJenkins!」のUTF-8エンコーディングです。
ユースケース2
このユースケースでは、メッセージの長さは正確に2ブロックです。これはラウンドケースと呼ばれます。ラウンドケースでは、量子化する部分的なブロックがないため、通常のCBCであるかのように進行します。パスワード、キー、およびIVは、ユースケース1と同じです。復号化される暗号文メッセージ(先頭に追加された8バイトIVを含む)は...
設定
Ciphertext = [40] 01 00 00 00 00 00 00 00 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83
which is encoded base64 as 'AQAAAAAAAABwdhJYTjgc4ZLKNPuaN8UKdfILRqHfVmDUXHZLUhnagw=='
これは、IV、最初のブロック、2番目のブロックに分解されます...
IV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
c[0] = [16] 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A
c[1] = [16] 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83
一般および最後から2番目のブロック
Decrypt(c[0]) = [16] 45 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72
m[0] := Decrypt(c[0]) XOR CV = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72
Next CV := c[0] = [16] 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A
最後のブロック:
このユースケースでは、最後のブロックは丸いです。
Decrypt(c[1]) = [16] 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83
m[1] := Decrypt(c[1]) XOR CV = [16] 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65
復号化プロセスの最後のステップは、最後の2つのブロックの放出です。ラウンドケースでは、順序を逆にすることはありません。最初にm[N-2]を放出し、次にm[N-1]を放出します。ユースケース2への適用...
m[0]を発行します
m[0] = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72
m1全体を放出する
m[1] = [16] 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65
まとめると、...の再構成された平文が得られます。
[32] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65
これは、「ダンス、どこにいても」のUTF-8エンコーディングです。
考慮すべきエッジケース。ここで提供されている2つのユースケースでは示されていない、2つのエッジケースがあります。
短いメッセージの場合でも、技術的には、暗号文の前のブロックとしてIVを使用することにより、暗号文の盗用を実装できます。ただし、IMHOは、このように暗号文を盗むことを使用することは、実装の複雑さが増すことは言うまでもなく、暗号強度への影響に関する研究が不足していることによって正当化されません。TurboPower LockBox 3では、メッセージが短いメッセージであり、連鎖モードがキーストリーミングモードではない場合、連鎖モードはCFB-8ビットとして扱われます。CFB-8ビットはキーストリーミングモードです。
長さがゼロのメッセージの場合、それは本当に簡単です。長さがゼロのプレーンテキストメッセージは、1対1から長さがゼロの暗号文メッセージにマップされます。IVは必要なく、生成も追加もされません。このマッピングは、連鎖モードおよび暗号(ブロックモード暗号の場合)とは無関係です。
PHPの実装に関する注意
警告
私はPHPプログラマーではありません。PHPを知りません。私がここで言うことはすべて、一粒の塩でとらえるべきです。
バイトの配列
PHP文字列を使用してバイトの配列を格納しているようです。これは私には危険に見えます。バイト値の1つがゼロだった場合はどうなりますか?それは文字列を短くしますか?その場合、strlen()はどのように動作しますか?PHPにバイトの配列であるネイティブデータ型がある場合、これはおそらくより安全です。しかし、私は本当に知りません。あなたがまだ気づいていないのであれば、私はあなたの注意を喚起しているだけです。おそらくそれは実際には問題ではありません。
mcrypt_decryptライブラリ
私はこのライブラリに精通していません。暗号文の盗用をネイティブにサポートしていますか?私はそうではないと思います。したがって、2つの可能な戦略があります。
CBCモードで、最後の2つのブロックを除くすべてのライブラリの復号化を呼び出します。私があなたに説明したように、最後の2つのブロックを処理します。ただし、これにはCVへのアクセスが必要です。APIはこれを公開しますか?そうでない場合、この戦略は実行可能なオプションではありません。
ECBモードで最後の2つのブロックを除くすべてのライブラリの復号化を呼び出し、CBCチェーンをロールします。実装はかなり簡単で、定義すると、CVにアクセスできます。
PHPでXORを実行する方法
他の誰かがこの質問への回答を投稿しましたが、現在それを撤回しています。しかし、彼は正しかった。PHPでバイト配列に対してXORを実行し、文字を1つずつ反復処理して、バイトレベルのXORを実行するように見えます。テクニックはここに示されています。