こんにちは!セキュリティチームで日々インシデントの最前線に立っているホワイトハッカーの私です。
今回は、Webアプリケーションや暗号通信の実装において、新人のエンジニアの皆さんが思わずハマってしまう「魔の罠」――CBCモードにおけるパディングオラクル攻撃(Padding Oracle Attack)についてお話しします。
「暗号化しているからうちは絶対に安全です!」――そう胸を張る開発現場に限って、この脆弱性が潜んでいたりします。難解な数式は一切抜きにして、身近な「合鍵と郵便受け」の例えを使いながら、攻撃のメカニズムから泥臭い実務での対策まで、優しく丁寧にお紐解いていきますね。
一歩ずつ、一緒に学んでいきましょう!
—
1. 家の鍵で例える「CBCモード」と「パディング」の基本
まずは、今回の主役である「CBCモード(Cipher Block Chaining)」と「パディング」が、現実の世界でどう動いているのかをイメージしてみましょう。
暗号を「ブロック」に区切って繋げるCBCモード
AESなどの共通鍵暗号は、データを一定の大きさ(例えば16バイトずつ)の「ブロック」という箱に区切って暗号化します。
しかし、ただ箱に分けるだけだと、同じデータは同じ暗号文になってしまい、泥棒にパターンを見破られてしまいますよね。そこでCBCモードでは、「前の箱の暗号化結果を、次の箱のデータに混ぜてから暗号化する」という数珠つなぎの手法をとります。これにより、同じデータであっても、置かれる場所によって全く違う暗号文に化けるため、安全性が劇的に高まるのです。
足りない分を埋める「パディング」のルール
ここで一つ問題があります。最後の箱(ブロック)のデータが、16バイトに満たなかったらどうするでしょうか?
例えば、あと3バイト足りない、といった場合です。この「足りない隙間を埋める作業」をパディングと呼びます。
実務でよく使われる「PKCS#7」という方式では、足りないバイト数を、その数値自体で埋めるというルールになっています。
- 3バイト足りないなら、隙間に
0x03というデータを3つ埋める。 - 16バイトまるまる足りないなら、16バイトすべてに
0x16を埋める。
これが、私たちが普段何気なく使っている暗号の裏側の仕組みです。
—
2. 泥棒の手口:パディングオラクル攻撃とは?
さて、ここからが本題です。この「パディングのルール」の隙をついたのが、パディングオラクル攻撃です。
ここでいう「オラクル(Oracle)」とは、神のお告げ、つまり「システムが教えてくれる返答(エラーメッセージ)」のことです。
アプリケーションがうっかり漏らす「神の声」
皆さんが作ったWebアプリで、次のようなエラーハンドリングをしたことはありませんか?
- 正しい暗号文が送られてきた時:「正常に復号できました」
- 壊れた暗号文が送られてきた時:「パディングエラーです(データの形式が正しくありません)」
攻撃者は、この「エラー内容の違い」を執拗に観察します。
もしアプリが「おっと、最後のパディングの数字が変だよ」と親切に教えてくれたら、攻撃者はこう考えます。
> 「エラーが出るということは、パディングのルール(最後の数字)が間違っているってことだな。じゃあ、暗号文の最後の1文字をちょっとずつ書き換えて、エラーが出なくなる瞬間を探してみよう」
1文字ずつ丸裸にされる恐怖
攻撃者は、暗号文を1ビットずつ総当たりで改ざんし、アプリに送りつけます。
そして、アプリが「あ、今のはパディングエラーが出なかったぞ(=正しく復号できた形になった)」と反応した瞬間、攻撃者は「裏で何が起きているのか(元の平文が何であるか)」を数学的に逆算できてしまうのです。
これを何回も繰り返すことで、攻撃者は鍵を持っていなくても、暗号化された中身(パスワードや個人情報)をすべて丸裸にしてしまいます。これがパディングオラクル攻撃の恐ろしい正体です。
—
3. 現場でやってしまいがちな「NGな実装」
では、実際の開発現場では、どのようなコードがこの脆弱性を生んでしまうのでしょうか。PHPを例に、やってはいけない実装を見てみましょう。
<?php
// 【危険な実装例】エラーメッセージでパディングの成否を教えてしまっている
function decryptDataUnsafe($ciphertext, $key) {
$iv = substr($ciphertext, 0, 16);
$encrypted = substr($ciphertext, 16);
// 復号処理
$decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
if ($decrypted === false) {
// NG: 復号失敗の原因(パディングエラーなのか、鍵違いなのか)を詳細に返している
return "エラー: パディングが無効です。";
}
return $decrypted;
}
?>
このコードでは、openssl_decrypt がパディングエラーなどを検知して false を返した際に、親切心(あるいはデバッグのしやすさ)から詳細なエラーを返してしまっています。これが、攻撃者に「オラクル(神の声)」を与えている決定的な原因になります。
—
4. 根本的解決:Encrypt-then-MAC とは?
「じゃあ、CBCモードはもう使えないの?」いいえ、そんなことはありません。
この攻撃の根本的な原因は、「中身が改ざんされているか(あるいはパディングが正しいか)を確認する前に、復号処理を行ってエラーを返していること」にあります。
この問題を一刀両断で解決する黄金律、それが Encrypt-then-MAC(暗号化してから認証コードを付与する) というアプローチです。
対策のステップ
1. 暗号化(Encryption):まずはデータをしっかりと暗号化する。
2. 署名(MAC):できた暗号文に対して、改ざん検知用のタグ(HMAC)を秘密裏に計算してくっつける。
3. 検証(Verify):アプリにデータが届いたら、復号する前に必ずHMACを使って「このデータは途中で改ざんされていないか?」を検証する。
もし検証の段階でデータが改ざんされていれば、その時点で即座に処理を弾き、絶対に復号処理(およびパディングのチェック)を行わないようにします。これによって、攻撃者は「オラクル(手がかり)」を一切得られなくなるため、攻撃が完全に成立しなくなるのです。
—
5. 実務で使える!安全な実装サンプル(PHP)
それでは最後に、モダンなフレームワークや安全なライブラリが内部で行っているような、改ざん検知を組み合わせた安全な実装の形を見てみましょう。
<?php
/**
* 【安全な実装例】Encrypt-then-MACパターンの概念コード
* 復号する前に必ずHMACで改ざん検知を行い、エラーメッセージは一律にする
*/
function decryptDataSecure($inputData, $encryptionKey, $macKey) {
// 1. 入力データから暗号文とMAC(署名)を分離する
$ciphertextWithIv = $inputData['ciphertext'];
$providedMac = $inputData['mac'];
// 2. 【最重要】復号する前に、送られてきたMACが正しいか厳密に検証する(タイミング攻撃対策も含む)
$calculatedMac = hash_hmac('sha256', $ciphertextWithIv, $macKey, true);
if (!hash_equals($calculatedMac, $providedMac)) {
// 改ざんされている、または不正なデータの場合は即座に拒否
// 攻撃者にヒントを与えないため、エラー内容は常に一律にする
return false;
}
$iv = substr($ciphertextWithIv, 0, 16);
$ciphertext = substr($ciphertextWithIv, 16);
// 3. 検証をクリアしたものだけを復号する
$decrypted = openssl_decrypt($ciphertext, 'aes-256-cbc', $encryptionKey, OPENSSL_RAW_DATA, $iv);
if ($decrypted === false) {
// 万が一の失敗時も、詳細な原因はログにだけ残し、外部には一律のエラーを返す
return false;
}
return $decrypted;
}
?>
実装時の大切なポイント
- エラーメッセージは統一する:「パディングエラーです」「鍵が違います」といった詳細な違いをユーザーに見せず、「不正なリクエストです」などの決まったメッセージを返すように徹底しましょう。
- 可能ならAEAD暗号を使う:もし新しくシステムを設計できるのであれば、AES-CBCモードではなく、改ざん検知が最初から組み込まれている AES-GCM などの「AEAD(Authenticated Encryption with Associated Data)」と呼ばれるモードを選ぶのが、現代のセキュリティにおけるベストプラクティスです。
—
まとめ
今回は、パディングオラクル攻撃のメカニズムと、その防ぎ方についてお話ししました。
- CBCモードのパディングの仕組みと、アプリが返すエラーメッセージが攻撃者の「目と耳」になってしまうこと。
- 根本的な対策は Encrypt-then-MAC(検証を先に行うこと) であり、余計なエラー情報を外に出さないこと。
セキュリティの基本は「相手に情報を与えないこと」です。一歩ずつ、こうした仕組みの裏側を理解していくことで、皆さんが作るシステムはより堅牢で信頼性の高いものになっていきます。
日々の開発、本当にお疲れ様です。一緒にセキュアなWebの世界を作っていきましょう!
コメント