𝗖𝗕𝗖 ビットフリッピング攻撃の解説
暗号化されているからといって、データが改ざんから安全であるとは限りません。
多くの開発者がこの間違いを犯します。「攻撃者がデータを読み取れなければ、変更もできない」と考えてしまうのです。しかし、これは間違いです。暗号学において、「秘匿性(Secrecy)」と「完全性(Integrity)」は別個のタスクとして扱われます。
CBCビットフリッピング攻撃は、このことを証明しています。攻撃者は秘密鍵を知らなくても、データを変更できてしまうのです。
仕組みは以下の通りです:
AESはデータを一つの大きな塊として暗号化するのではなく、16バイトずつのブロックに分割します。CBCモードでは、これらのブロックが連鎖(チェーン)します。第1ブロックの暗号化出力が、第2ブロックの平文(plaintext)に混ざり合う仕組みです。
この連鎖が、復号時に脆弱性を生みます。第2ブロックの元のテキストを得るために、サーバーはそれを復号し、第1ブロックの暗号文(ciphertext)と組み合わせます。
攻撃者はこれを悪用できます:
- 攻撃者が暗号化されたトラフィックを傍受します。
- 鍵は知らないため、データは意味不明な文字列に見えます。
- 第1ブロックの暗号文内の特定のビットを変更します。
- この改ざんされたデータをサーバーに送信します。
- サーバーが復号する際、第1ブロックでの変更が第2ブロックの計算結果を変化させます。
攻撃者はメッセージを読む必要はありません。単にビットを反転させて、最終的な結果を書き換えるだけです。
セッション管理に暗号化されたクッキーを使用している古いWebアプリを例に考えてみましょう。クッキーの内容が以下のような場合を想定します: userid=994;role=user;
攻撃者がこのクッキーを傍受し、暗号文のビットを反転させます。そして、サーバーが受理するまで何度もリクエストを送り続けます。サーバーは「データが復号できるか」しかチェックしないため、改ざんされたテキストを処理してしまいます。すると、突然、復号された文字列が次のように変わります: userid=994;role=admi;
これで攻撃者は管理者権限を手に入れます。彼らは鍵も元のクッキーも一度も読んでいません。
間違いは、「暗号化が完全性を保証する」と思い込んでしまうことです。
- 秘匿性(Confidentiality)は、データの読み取りを防ぎます。
- 完全性(Integrity)は、データの改ざんを防ぎます。
これを防ぐには、AES-GCMのような「認証付き暗号(Authenticated Encryption)」を使用してください。これは暗号学的タグ(cryptographic tag)を作成します。このタグは「封印」のような役割を果たします。攻撃者がたとえ1ビットでも変更を加えると、封印が壊れます。サーバーは即座にデータを拒否します。
どうしてもCBCを使用しなければならない場合は、Encrypt-then-MACアーキテクチャを採用してください。暗号文に対して認証コードを作成し、復号を開始する前にそれを検証します。
「秘密のデータ」が、常に「信頼できるデータ」であるとは限りません。データを使用して意思決定を行う前に、そのデータが改ざんされていないことを必ず証明してください。
CBCビットフリッピングの解説:なぜ暗号化だけでは完全性が保証されないのか
暗号化は、データの機密性を確保するために頻繁に使用されます。しかし、多くの開発者が、データが暗号化されていれば、そのデータが改ざんされていないことも保証されると誤解しています。
これは重大な誤解です。暗号化は**機密性(Confidentiality)**を提供しますが、**完全性(Integrity)**を保証するものではありません。
この違いを理解するための最も優れた方法の一つが、CBC(Cipher Block Chaining)モードにおけるビットフリッピング攻撃です。
理解を深める:機密性と完全性の違い
セキュリティの3要素(CIA)において、機密性と完全性は異なる概念です。
- 機密性 (Confidentiality): 許可されていない人がデータの内容を読み取ることができないこと。暗号化の主な目的です。
- 完全性 (Integrity): データが、送信時または保存時から未承認の形で変更されていないこと。
CBCモードでデータを暗号化すると、たとえ攻撃者が暗号文の内容を解読できなくても、暗号文の特定のビットを操作することで、復号された平文の内容を意図的に変更できる場合があります。
CBCモードの仕組み
CBCモードでは、各ブロックの暗号化プロセスが前のブロックの暗号文に依存しています。
暗号化のプロセスは以下の通りです:
- 最初のブロックは、初期化ベクトル(IV)とXOR演算されます。
- その後、各ブロックは、前のブロックの暗号文とXOR演算され、それから暗号化関数(例:AES)が適用されます。
復号プロセスは、その逆になります。あるブロック $P_i$ の平文を求める式は次のようになります:
$$P_i = D_k(C_i) \oplus C_{i-1}$$
ここで:
- $P_i$ は現在の平文ブロック
- $D_k$ は鍵 $k$ を使用した復号関数
- $C_i$ は現在の暗号文ブロック
- $C_{i-1}$ は前の暗号文ブロック(最初のブロックの場合はIV)
この式から、非常に重要なことがわかります。前の暗号文ブロック ($C_{i-1}$) のビットを操作すると、現在の平文ブロック ($P_i$) の対応するビットが直接変化するということです。
ビットフリッピング攻撃
ビットフリッピング攻撃では、攻撃者は暗号文の特定のビットを反転させます。
攻撃者が $C_{i-1}$ のあるビットを反転させると、復号プロセスにおいて $P_i$ の同じ位置にあるビットも反転します。
攻撃者は以下のことを知っています:
- 暗号文の構造。
- ターゲットとなる平文の形式(例:
user=guest)。 - どのビットを反転させれば、平文が望ましい値に変わるか。
攻撃者は鍵を知る必要も、元の平文を解読する必要もありません。単に、暗号文を「操作」するだけでよいのです。
実践的な例
次のようなクッキー(Cookie)が、CBCモードで暗号化されて保存されていると仮定しましょう。
元の平文:
user=guest;role=user
攻撃者は、このクッキーを user=guest;role=admin に書き換えたいと考えています。
- 攻撃者は、暗号文を解析して、
role=userという文字列がどのブロックに含まれているかを特定します(あるいは、構造から推測します)。 - 攻撃者は、
role=userのuに対応する暗号文ブロック $C_{i-1}$ のビットを見つけます。 - 攻撃者は、そのビットを反転させます。
結果:
- $C_{i-1}$ のビットを操作したため、復号された $P_{i-1}$ ブロックは破壊され、ランダムなゴミデータになります。
- しかし、攻撃者の目的は $P_i$ ブロック(
role=adminが含まれるブロック)を操作することです。 - $P_i$ ブロックは、攻撃者が操作した $C_{i-1}$ とXORされるため、攻撃者が意図した通りに
role=adminと復号されます。
サーバーは、暗号文を復号し、role=admin という有効な(しかし不正な)権限を持つユーザーとして処理してしまいます。
ビットフリッピング攻撃を防ぐ方法
暗号化だけで完全性を確保しようとするのは危険です。これを防ぐには、**認証(Authentication)**を組み合わせる必要があります。
1. HMAC (Hash-based Message Authentication Code) を使用する
「Encrypt-then-MAC」という手法が推奨されます。
- データを暗号化します。
- 暗号文全体に対して、秘密鍵を使用したHMACを計算します。
- 暗号文とHMACの両方を送信します。
受信側では、まずHMACを検証します。もし攻撃者が暗号文の1ビットでも変更していれば、HMACの検証が失敗し、復号プロセスに進む前にデータを破棄できます。
2. AEAD (Authenticated Encryption with Associated Data) を使用する
現代的な暗号化方式では、暗号化と認証を同時に行う AEAD モードを使用するのがベストプラクティスです。
- AES-GCM (Galois/Counter Mode): 最も一般的で高速なAEADモードです。
- ChaCha20-Poly1305: 高速で、ソフトウェア実装において非常に強力です。
これらのモードを使用すると、データの機密性と完全性が単一の操作で保証されます。もし暗号文が改ざんされていれば、復号プロセス自体がエラーを返し、不正な平文が生成されることはありません。
まとめ
- 暗号化は機密性を確保するためのものであり、完全性を保証するものではありません。
- CBCモードなどの古いモードでは、暗号文の操作によって平文を意図的に変更できるビットフリッピング攻撃に対して脆弱です。
- 常に AEAD モード(AES-GCMなど)を使用するか、Encrypt-then-MAC のパターンを採用して、データの完全性を検証するようにしてください。