CTFにおけるCrypto入門とまとめの1つです。
AES
- AES = ブロック暗号コンペで選定されたRijandaelという対称暗号
- ブロック暗号
- 一定長のブロックに分けて暗号化
- 構成要素
- パディング方式: ブロック毎に分けたときに端数をどのようにブロックに丸めるかを決めたもの
- ブロック暗号利用モード: 分けたブロックに対してどのようにブロック暗号を適用していくかを決めたもの
- 暗号化方式: ブロック暗号のコア部分。ブロックをどのように暗号化するかを決めたもの
Rijandael暗号
- 図で書くと https://x.com/meowricator/status/1893782242561851533
- SPN構造: 復号時に暗号化時に使った各種変換の逆関数を使って、逆関数を逆順で全く同じように適用して復元する
- 暗号化アルゴリズム(AES-128)
- 128ビットのキーから11個の個別の128ビットのラウンドキー rk を作成
- 4×4のマトリクスで、Rijandael暗号では4×4のマトリクスを元に攪拌していく
- AddRoundKey: plaintext xor rk[0]
- Main Round: 以下を9回ループ
- SubBytes: Sボックス(ルックアップテーブル)を使用して入力ブロックを1byte単位で置換
- 線形関数で近似されにくい方法で変換していて、非線形性を達成する手順
for i in range(len(s)): for j in range(len(s[i])): s[i][j] = sbox[s[i][j]]みたいに置換- これがないSPN構造のおかげで計算全体が線形になるので、ct = A * pt + keyで表現でき、線形計算でpt,ctの組からkeyが求められる http://blog.lkan.onl/posts/defcamp_2025/#scroll-crypto-19-solve
- ShiftRows: 入力ブロック内部データを4*4行列とみなして転置
- MixColumns: 入力ブロック内部データを4*4行列とみなして列ごと操作
- AddRoundKey: これまでの状態 xor rk[i]
- SubBytes: Sボックス(ルックアップテーブル)を使用して入力ブロックを1byte単位で置換
- Last Round: SubBytes → ShiftRows → AddRoundKeyをやって、その結果が暗号文
- 128ビットのキーから11個の個別の128ビットのラウンドキー rk を作成
- 用語
- State: 暗号化処理を行っているブロックのバッファのようなもの
- S-Box: Stateの変換をするための置換テーブル
- 置換テーブルの作り方がまずいと、差分解読法・線形解読法とかで破られてしまう
- 計算自体は拡大体GF(28)上で計算される
- 復号化処理。暗号化処理を各処理逆関数を使って逆に適用していく
- ブルートフォースでAES解読
- 電力使用量から解析できる
- ラウンド数が少ない場合は攻撃可能らしい
- AESの実装が改変されていて解けちゃう
- TCP1PCTF 2024 - What’s the Worst That Could Happen http://blog.lkan.onl/posts/TCP1PCTF/#whats-the-worst-that-could-happen
- AESの実装が改変されており、shift_rows、inv_shift_rowsの方向が間違っていると、4bytes毎に暗号化されてしまう(16bytes毎ではなく)
- TCP1PCTF 2024 - What’s the Worst That Could Happen http://blog.lkan.onl/posts/TCP1PCTF/#whats-the-worst-that-could-happen
パディング方式
- ゼロパディング: paddingとしてゼロ0x00埋めする方式
- PKCS#7 pkcs7 Padding
- PKCS(: Public-Key Cryptography Standards)の7番目として決められた、暗号メッセージ構文に関する仕様の中で言及されているパディング方式
- 1ブロックは255バイトまで
- パディングとして0x00ではなくて、パディング長を入れこむ
- 仮に8bytesでアラインメントするなら…
AAAAなら4つ入れる必要があるのでAAAA\x04\x04\x04\x04みたいな感じ- ピッタリなら新しくブロックを作ってパディング長を入れる。
BBBBBBBBならBBBBBBBB\x08\x08\x08\x08\x08\x08\x08\x08
ブロック暗号モード
必ずしも+AESではないみたい。
ECBモード
- ECB:Electronic CodeBlock ブロックごとに単純に暗号化する
- 平文のブロックで同じものがあれば同じような暗号文が生成されてしまう
- 暗号化 encrypt([平文block])
- 復号化 decrypt([暗号block])
- 鍵はあるけどivがないならECBかも
- 攻撃テク
- ECBでは16バイトのブロックごとに暗号化されるので、暗号化文字列を切り貼りして16バイト単位で差し替えが可能
- ブロック長を意識して暗号文を作成して、切り貼りしていくことで任意の復号文を作る
- 例えばjson形式を暗号化していたりすればスペースは無視されるので、スペースとかを利用して任意の文字だけを含むブロックを作ったりしながら頑張る
- https://qiita.com/kusano_k/items/8a63b97d1427ef2e3369#imaginary
- ECBではブロックごとに平文と鍵が同じであれば、同じ暗号文が出力される
- 同一の鍵で平文が操作できるときに、全通りのブロックを作って比較して特定みたいなことができる
- SECCON Beginner CTF 2025 01-Translator
- 同一の鍵で平文が操作できるときに、全通りのブロックを作って比較して特定みたいなことができる
- 同じ平文で同じ暗号文になるので頻度分析に脆弱
- (若干、やや大変なだけなのと、人間が判断したりする感じで好みは分かれそう)
- https://github.com/ImaginaryCTF/ImaginaryCTF-2025-Challenges/tree/main/Crypto/extra-crunchy-bits
- ECBでは16バイトのブロックごとに暗号化されるので、暗号化文字列を切り貼りして16バイト単位で差し替えが可能
CBCモード
- CBC:Cipher Block Chaining 暗号化する前に平文を1つ前のブロックとXORして暗号化する
- 最初のブロックは1つ前というのが無いのでIV(初期化ベクトル)というのを初期状態として用意する
- IVは復号時にも必要になるが、鍵という扱いはされないみたい
- 人に依って意見は分かれそうな雰囲気があるが、個人的には鍵がいくつもあるメリットもないので秘密ではないと考えておくスタンスでよさそう
- 暗号化 encrypt([平文block] xor [IVか1つ前暗号block])
- 復号化 decrypt([暗号block]) xor [IVか1つ前暗号block]
- 最初のブロックは1つ前というのが無いのでIV(初期化ベクトル)というのを初期状態として用意する
- ivを外部注入できる場合
- 最終的な復号文の先頭16bytesを自由に変更することができる
- 復号時に先頭16bytesにAESの復号化処理がかかった後、xor ivされる。ここでAという風に復号化され、Bに復号化したい場合は、ivをiv xor A xor Bとしてやればxorでうまく相殺されて、Bに書きかえることが可能になる
- Bit-flipping attack
- CBCモードでは復号時に、decrypt関数に通した後1つ前の暗号のblockとxorをするので、とある暗号ブロックを1bitフリップさせることで、その次の暗号ブロックの対応bitもフリップさせられる
- 例えば復号したら"test1"になるようにしておいて、その前の暗号ブロックの対応ビット部分を"test1" xor "admin"にしておけば復元したらadminになっている。
- 副作用で色々壊れるかもしれないが、それでも問題なければ攻撃につなげることが可能
- picoCTF2021 [Web Exploitation] writeup - 好奇心の足跡
- https://tryhackme.com/r/room/flip 練習に便利
- CBCモードでは復号時に、decrypt関数に通した後1つ前の暗号のblockとxorをするので、とある暗号ブロックを1bitフリップさせることで、その次の暗号ブロックの対応bitもフリップさせられる
- Padding Oracle Attack
- Bit-flipping attackの発展形
- CBCモードにおいて復号するときに、PKCS#7パディングが不正であるエラーが発生する場合に有効なテクニック
- 名前にもあるように復元後のパディングチェックが成功したか失敗したかをオラクルとして攻撃を進めていく
- 復元への動作原理
- 暗号文の最終ブロックを特定することをまず考える
- 最後から2番目のブロックを特定したい場合は最終ブロックを削除して同じことを行えばできるので、最後ブロックの復元だけ考えればいい
- 最終ブロックc[N]を特定するために、最終ブロックc[N-1]部分を改変してpayloadを送る
c[1],c[2]...,改変c[N-1],c[N]- c[N-1]前まではどうでもいいが、c[N]はそのままにしておくこと
- c[N]は元のままなので、復号用関数に通した結果は解析したいものと同じになるdec(c[N])
- 攻撃で求めるのはdec(c[N])の内容になる。この内容が分かれば平文p[N]がdec(c[N]) xor c[N-1]で求まる
- c[N-1]を00...0000から00...00ffまで全探索して
c[1],c[2]...,改変c[N-1],c[N]のようにして送る- この改変後のc[N-1]をc'[N-1]としておこう
- 送った後のN番目のブロックの復号文はdec(c[N]) xor c'[N-1]となっている
- そして重要なのが『paddingを検証をしているとこのうち少なくとも1つはpadding検証は成功で返ってくるはずである』
- ここの理解が難しい!
- xorしているので、N番目のブロックの復元後は??????00~???????ffが均等に現れてくるはずである
- この復元後に対してpaddingチェックが確実にOKとなるのは???????01の時だけである
- N番目の元々の原文が何であるかというのは全く関係ない。paddingチェックという観点だけで見ると末尾が0x01になっていればチェックは通る
- (もしかしたらたまたま末尾が0x0202みたいになってて成功する場合もあるかも)
- つまり、255通りのうち成功で返ってきているならば、その入力において復号したdec(c[N]) xor c'[N-1]の末尾1byteが0x01である可能性が非常に高いと言える
- もっと言えば、255通りのうち1つだけ成功で返ってきているならば確実にその入力において復号したdec(c[N]) xor c'[N-1]の末尾1byteは0x01である
- これで
dec(c[N]) xor c'[N-1] = ???????01となるc'[N-1]が特定できたので、dec(c[N])の末尾1byteは0x01 xor c'[N-1][:-1]であると特定できた - ここまで理解できていればだいぶ完全理解まで近い
- これでdec(c[N])の末尾1byteは分かったのでは次は末尾2byte目を特定しよう
- c[N-1]を00...0000XXから00...00ffXXまで全探索して
c[1],c[2]...,改変c[N-1],c[N]のようにして送る- XXはなんだと思うかもしれないが、padding検証のことを加味して考えてみる
- 末尾1byte特定の場合は最終的に0x01で終わることを考えたが、次は末尾が0x0202で終わることを考えよう
- この場合のXX部分は、既にdec(c[N])の末尾1byteが特定されているので、0x02にするためには XX = dec(c[N])[:-1] xor 0x02を置けばいい
- これでXXの1つ上のbyteを00からffまで全探索すれば末尾が
0x0202となる1つだけがpadding検証は成功した状態で返ってくることになる
- padding検証が成功でかえってきた入力を見れば、末尾1byte目の特定と同じ要領で末尾2byte目まで特定できたことになる
- c[N-1]を00...0000XXから00...00ffXXまで全探索して
- このような感じでブロックサイズ16byteのdec(c[N])を特定していく
- dec(c[N])が特定できればもともとのc[N-1]を使って、平文p[N]を求めることができた
- ivが用意されていればそれもうまく弄って全文復元可能
- 暗号文の最終ブロックを特定することをまず考える
- 改ざんへの実行原理
- まずは復元時と同様にして、改ざんしたいブロックのdec(c[x])を求める
- そしたら、改ざんしたい値に合わせてc[x-1]を(改ざん後としたい文字列) xor dec(c[x])にすれば改ざん完了
- こうするとx-1番目の内容も変わってしまうが、変わった後のdec(c[x])も求めることができるので、それを使ってさらに前の文章も修正できる
- ivが用意されていればそれもうまく弄って全文改ざん可能
- https://writeups.thebusfactoris1.online/posts/2025-09-05-decryption-execution-service-writeup
- Padding Oracle Attack 分かりやすく解説したい - Attack All Around
- 実装
- ivが隠匿されていて、暗号化を任意の値で行えて結果が取得できる場合にivを特定可能
b'A'*16+b'\x00'*16+b'A'*16というのを復号する- 復号は
decrypt([暗号block]) xor [IVか1つ前暗号block]のように行われるので...- 1ブロック目の復号結果は
decrypt(b'A'*16) xor IV - 3ブロック目の復号結果は
decrypt(b'A'*16) xor b'\x00'*16よりdecrypt(b'A'*16)
- 1ブロック目の復号結果は
- つまり、1と3ブロック目の復号結果をxorするとIVが得られる
decrypt(b'A'*16) xor IVxordecrypt(b'A'*16)=IV
- https://github.com/rerrorctf/writeups/blob/main/2025_02_07_BITSCTF25/crypto/alice_n_bob_in_wonderland/writeup.md
- 同じ鍵が使われていて、何度も復号化できる場合は、復号先を任意の値に変更可能
- SECCON Beginners 2024 ARES https://qiita.com/toha/items/65cfc6144128c5988e22#ares
- Key reuse https://github.com/jvdsn/crypto-attacks/tree/master/attacks/cbc_and_cbc_mac
- EAM, ETM, MTEの鍵が再利用されていると脆弱瀬
CFB: Cipher FeedBack Mode / OFB: Output-FeedBack mode
- CFB: Cipher FeedBack Mode
- 暗号復号化
- 暗号化 encrypt([IVか1つ前暗号block]) xor [平文block]
- 復号化 encrypt([IVか1つ前暗号block]) xor [暗号block]
- 暗号化と復号化が全く同じ
- レプレイ攻撃可能
- 暗号復号化
- OFB: Output-FeedBack mode
- keyとIVから暗号用の鍵ストリームを作って、それと平文のxorを暗号とする
- 暗号化/復号化は同一
鍵作成鍵 = encrypt([IVか1つ前の鍵],[key])xorencrypted = plain xor 鍵
- https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%BC%E3%83%89 のグラフを見るのがいい
- keyが共通のとき、IVの代わりに1つ目の鍵を指定することで鍵ストリームを途中から生成できる
- AES の OFB モードは wikipedia の図を見ればわかるように、 ciphertext をいじることによって復号結果を調整することが簡単にできます。そのため Padding Oracle Attack を使って平文 (ここでは GCM モードのタグ、 nonce、暗号文) が求まります。 https://blog.y011d4.com/20221113-seccon-ctf-writeup#witches_symmetric_exam
CTR / GCM
- CTR: CounTeR カウンタを使ってブロック暗号をストリーム暗号として使う
- カウンタ/疑似乱数 = [nonce(毎回作り直すランダムな値)] + [ブロックごとにインクリメントするブロック番号]
- 疑問点: ストリーム暗号になってる?
- 例えばnonceが123456789であれば、ブロックごとに
1234567890001,1234567890002,1234567890003,...を使うイメージ - 暗号化 encrypt([疑似乱数]) xor [平文block]
- 復号化 encrypt([疑似乱数]) xor [暗号block]
- 攻撃
- 同一KEY かつ 同一NONCEが使われている場合は Enc(x) xor Enc(y)は
encrypt([疑似乱数])部分が相殺されて、x xor yとなる。- 平文が分かっていれば、Enc(x) ^ x ^ yにすればyの暗号化に変換可能
- 暗号化と復号化は同一の処理であるため、カウンタが同じであれば暗号化処理を復号化処理として利用可能
- カウンタが何等かの問題でループする場合
- 同じKEY, NONCEを使っている場合、カウンタをいい感じにループさせて暗号化時に使った初期カウンタと同じものが用意できれば復号可能
- 同一KEY かつ 同一NONCEが使われている場合は Enc(x) xor Enc(y)は
- CTRはストリーム暗号として使えるので、平文はブロックサイズにpaddingされてなくてもよく、また、暗号文もpaddingされずに同じサイズ長で出てくる
- (全部でそうなのか分からないが、少なくとも、pycryptodomeだとそうなった。Counterでブロック長が指定されていても出力サイズはpaddingされない)
- 他攻撃 https://github.com/jvdsn/crypto-attacks/tree/master/attacks/ctr
- CRIME
- Separator Oracle
- GCM: Galois/Counter Mode, ガロア認証(GHASH関数)+カウンターモードで暗号化。暗号化/復号化と同時にデータが正しい人に作成されて改ざんされてないことも確認可能
- 暗号自体はCTRモードが使用されるので、CTRモードの悪用が同様に適用可能
- 暗号化すると、暗号文(とIV)に加えて認証タグと呼ばれるMACの役割を果たすものが出てくる
- タグ長を検証する必要がある
- AES-GCMモード | 調査研究/ブログ | 三井物産セキュアディレクション株式会社
- CakeCTF 2022 Writeup | y011d4.log
- 暗号文を検証するタグの長さが未検証で、長さを1バイトとかにしても検証が通る
- この問題文では最後にタグの長さが16バイトであることを検証しているので、1バイトにして0x00-0xffで試して成功するものを見つけたら、2バイトにして[1バイト目の正しいもの]+0x00-0xffで試して成功するものを見つけたら、…で16バイトのタグを見つけ出す
- IVの重複はダメ(他は非推奨レベルだけど、GCMでは一発アウトらしい)
- CTRとGCM共通かもしれないが、nonceが使いまわされているとダメ https://github.com/JorianWoltjer/challenges/blob/main/1337up-live-2024/conversationalist/solve/WRITEUP.md
- #170548 Ruby OpenSSL Library - IV Reuse in GCM Mode
- IVは再利用せず、毎回作ったほうがいい?
- javascriptでAES-GCMを使っているとき、
decipher.final()を呼び出さないと検証が行われず、AES-CTRと同じになる。つまり、ビット府リッピングが可能になる - Partitioning Oracle Attack
- 復元が成功したかどうかをオラクルにして色々明らかにする攻撃
- https://eprint.iacr.org/2016/475.pdf
- https://jovi0608.hatenablog.com/entry/20160524/1464054882
- AES-GCM-SIV: 多項式生成の方式を変更してIVが再利用されても影響が出ないようにする仕組みをGCMに組み込んだ仕様
- Forbidden Attack https://github.com/jvdsn/crypto-attacks/blob/master/attacks/gcm/forbidden_attack.py
XCBモードへの攻撃
- IEEE 1619.2に記載があるブロック暗号アルゴリズム
- 2024年に破られたっぽい?
- CTF問題
- N1CTF 2024 - Twisting https://github.com/hash-hash/My-CTF-Challenges/tree/main/n1ctf2024/Twisting
XTS: XEX encryption mode with tweak and ciphertext stealing
- 現行最強(?)のストリーム暗号モード
- C ← XTS-AES-Enc(K,M,T)
- K: ブロック暗号用の鍵で、2つの鍵K1,K2を使い、K=K1||K2
- M: 平文
- T: Tweak(調整値)
- 例えばハードディスクなどのストレージデバイスの場合、セクタ番号が利用される
- C ← XTS-AES-Enc(K,M,T)
- 問題点
- XTSは改竄耐性が無い。同一キー・同一 tweak・同一ブロックインデックスなら、同じ平文ブロック ⇒ 同じ暗号文ブロック になる (Securinets CTF Quals 2025 XTaSy)
- つまり、ECBのようにそのブロックを入れ替えることによって平文を改ざん可能(1ブロックは16bytes)
- XTSの末尾だけ特殊(ciphertext stealing, CTS)なので、改ざん対象のブロックが「最後の2ブロック以外」になるように寄せておくこと
- CTS: 最後のブロックが16バイト未満のとき、XTSは後ろの端数ブロックの分だけ、直前のブロックの末尾の暗号文バイトを盗む -> Securinets CTF Quals 2025 Exclusive
- XTSは改竄耐性が無い。同一キー・同一 tweak・同一ブロックインデックスなら、同じ平文ブロック ⇒ 同じ暗号文ブロック になる (Securinets CTF Quals 2025 XTaSy)
- IEEE 1619 IEEE Standard for Cryptographic Protection of Data on Block-Oriented Storage Devices で規定
- 網羅的な資料
- https://www.cryptrec.go.jp/exreport/cryptrec-ex-2902-2019.pdf
- https://www.cryptrec.go.jp/exreport/cryptrec-ex-2801-2018.pdf
- BitLocker, FileVault2もこれを使ってる
- BitLockerは
[Windows8,Windows10 1511)ではAES-CBC
- BitLockerは
- 分かりやすい図 https://www.kingston.com/jp/blog/data-security/xts-encryption
他
- AES-CFB8: Advanced Encryption Standard –Cipher Feed Back 8 bit
- ZeroLogon CVE-2020-1472 で悪用された。昔のADではこれに加えてivが0で固定だったため、平文が全部0のものを与えると暗号文をとある1文字から成るものに強制できた
- PCBCモード + Padding Oracle Attack
- SECCON CTF 2021 - cerberus https://blog.y011d4.com/20211212-seccon-writeup#cerberus
他、攻撃メモ
- Square Attack https://hackmd.io/@Giapppp/square_attack https://www.davidwong.fr/blockbreakers/square.html?
- 複数の暗号化モードのAESを組み合わせた
- 暗号化・復号化をXORの式にしてガチャガチャやるといい感じになるかも
- zer0pts CTF 2021 3-AES https://hackmd.io/@theoldmoon0602/SJ3X1f0zu
- 色々書いてある攻撃論文 https://iacr.org/archive/asiacrypt2001/22480210.pdf
- 暗号化・復号化をXORの式にしてガチャガチャやるといい感じになるかも
- Fernet: あまり自信が無いが、誰か(誰?)が決めた推奨の暗号化プリミティブ(つまり、オススメ暗号スイートとその格納仕様)
- AES-CBC-128で暗号化し、SHA-256を使用したHMAC
- Formatはここ https://github.com/fernet/spec/blob/master/Spec.md