現場のエンジニア諸君、お疲れ様。今日取り上げるのは、暗号実装における「古典にして最凶のトラップ」、パディングオラクル攻撃だ。
教科書には「ブロック暗号のCBCモードは安全」なんて書かれているかもしれないが、それは「正しく実装されていれば」という大きな前提がある。現実のインシデント現場では、この「正しく」の解釈を間違えて、数百万件の個人情報を流出させるケースを何度も見てきた。
今日は、なぜCBCモードが脆弱性を生むのか、そして我々が今すぐどう設計を変えるべきかを、泥臭い実務の視点から解説する。
—
1. なぜ「CBCモード」は攻撃者のメッカなのか
ブロック暗号(AESなど)は、固定長のブロック(AESなら16バイト)単位で処理される。だが、実際のデータは16バイトの倍数とは限らない。そこで登場するのが「パディング」だ。代表的なPKCS#7パディングでは、足りない分を「足りないバイト数」で埋める。
攻撃の原理:サーバーからの「悲鳴」を聞き逃すな
パディングオラクル攻撃の肝は、サーバーがパディングエラーを詳細に返しすぎることにある。
攻撃者は、暗号文の末尾を少しずつ書き換えてサーバーに送りつける。サーバーが「パディングが不正です」というエラーを返せば、「あ、この書き換えでパディング構造が壊れたな」と推測できる。これを繰り返すことで、暗号鍵を知らなくても、1バイトずつ平文を復元できてしまうのだ。
「そんな細かいエラーを出すアプリなんてあるか?」と思うだろう? 実際には、例外スタックトレースをそのまま表示したり、ログイン失敗時に「パディングエラー」と明確にログ出力したりする実装は、掃いて捨てるほどある。
—
2. 根本的解決策:Encrypt-then-MAC (EtM)
この攻撃を食い止める唯一の確実な手段は、「暗号化した後に認証する(Encrypt-then-MAC)」ことだ。
暗号文を改ざんされた時点で、MAC(メッセージ認証コード)の検証に失敗し、復号処理自体が行われないようにする。パディングを確認する以前の問題として弾く、これが鉄則だ。
—
3. 実践:セキュアな実装コード
現代のWeb開発において、自前でAESを実装するのは「事故の元」だ。基本的には libsodium のような標準的で強力なライブラリを使うべきだ。PHPであれば openssl_encrypt ではなく、sodium_crypto_aead_aes256gcm_encrypt を強く推奨する。
以下は、PythonでAEAD(Authenticated Encryption with Associated Data)モードを用いたセキュアな実装例だ。
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# 鍵は環境変数から読み込む(ハードコードは厳禁!)
# 32バイト(256ビット)の鍵を準備する
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
def encrypt_data(plaintext: str):
# 12バイトのランダムなnonce(初期化ベクトル)を生成
nonce = os.urandom(12)
# 暗号化と同時に認証タグ(MAC)を付与する(これがEncrypt-then-MACに相当)
ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
return nonce + ciphertext
def decrypt_data(token: bytes):
nonce = token[:12]
ciphertext = token[12:]
# 復号時に認証が失敗すれば例外が発生する
# パディングオラクル攻撃はここで遮断される
return aesgcm.decrypt(nonce, ciphertext, None).decode()
# 運用上の注意点:
# 復号失敗時に「パディングエラー」「MACエラー」といった詳細をクライアントに返してはならない。
# 「何らかの不正な入力です」という汎用的なエラーのみを返すこと。
—
4. 運用エンジニアがすべき「最低限の防衛線」
コードの修正が間に合わない場合や、レガシーなシステムを抱えている場合は、インフラ層での防御を強化せよ。
- WAFによるトラフィック解析: AWS WAFやCloudflare等で、不自然なパディングエラーを引き起こすような「暗号文の末尾を細かく書き換えたリクエスト」を検知・ブロックするルールを適用する。
- エラーハンドリングの徹底: アプリケーションログには詳細なスタックトレースを残しても良いが、HTTPレスポンスには絶対に含めるな。 500エラーを返す際も、汎用的な「500 Internal Server Error」だけを返すようにカスタムエラーページを設定すること。
- 鍵のローテーション: もしCBCモードを使わざるを得ない場合でも、鍵を定期的に変更することで、攻撃者が復元できる平文の範囲を限定的に抑えられる。
—
最後に:セキュリティは「疑うこと」から始まる
エンジニアの諸君、いいか。暗号ライブラリのドキュメントに「安全」と書いてあっても、それをどう組み込むかで強度は180度変わる。
「パディングエラーを返していないから大丈夫」と過信せず、常に「もしこの暗号文が改ざんされたら、システムはどう反応するか?」という攻撃者の視点でコードを眺めてほしい。
もし君のプロジェクトで古いCBCモードのコードを見つけたら、それが「時限爆弾」だと思ってすぐにでもリファクタリングに着手することだ。それがプロフェッショナルとしての最低限の責任だぞ。
何かあれば、またいつでも相談に来い。現場からは以上だ。
コメント