Upgrade to Pro — share decks privately, control downloads, hide ads and more …

なぜ秘密の比較に hash_equals を使うのか ー内部実装と実践ガイド / why us...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

なぜ秘密の比較に hash_equals を使うのか ー内部実装と実践ガイド / why use hash equals for secret comparison internals and practical guide

Avatar for コドモン開発チーム

コドモン開発チーム

February 23, 2026
Tweet

More Decks by コドモン開発チーム

Transcript

  1. 16 Linux man pageの警告 “ Do not use memcmp() to

    compare confidential data, such as cryptographic secrets, because the CPU time required for the comparison depends on the contents of the addresses compared, this function is subject to timing-based side-channel attacks. (機密データ(暗号シークレットなど)の比較にmemcmp()を使用しないでくだ さい。比較にかかるCPU時間は比較されるアドレスの内容に依存するため、こ の関数はタイミングベースのサイドチャネル攻撃を受けやすいです。)
  2. 18 攻撃の3ステップ Step 1 文字列長の特定 長さ一致時のみmemcmpが実行され、わずかに処理時間が長くなる ▼ Step 2 1文字目の特定

    正しい文字で2文字目比較に進み、処理時間が増加 ▼ Step 3 2文字目以降も同様 全文字を1文字ずつ特定していく
  3. 23 ベンチマーク環境 ⚫ 環境 : Docker(Apple Silicon) ⚫ 文字列長 :

    256文字 ⚫ 試行回数 : 1,000万回 × 7ラウンド(中央値を採用) ⚫ 測定対象 : 不一致位置を変えて === と hash_equals を比較
  4. 24 比較するPHPバージョン ⚫ PHP 5.6 : hash_equalsが初めて導入されたバージョン ⚫ PHP 7.4

    : PHP 7系の最終安定版 ⚫ PHP 8.4 : PHP 8系(JIT無効) ⚫ PHP 8.4 : 最新版(JIT有効)←ライブデモ 時間の都合上、ライブデモは PHP 8.4(JIT有効) のみ実施。 他のバージョンは事前に実行した結果を利用しています。 実行するコードはこちら
  5. 25 測定パターン ⚫ 1文字目不一致 : 最初の文字だけ違う → 最速 ⚫ 最後不一致

    : 256文字目で不一致 → 最遅 ⚫ 差分 : この2つの差がタイミング攻撃の根拠
  6. 27 ベンチマーク結果(=== 演算子) PHP Version 1文字目不一致 最後不一致 差分 5.6 14.09

    ns 23.20 ns 9.11 ns 7.4 6.47 ns 9.55 ns 3.08 ns 8.4(JIT無効) 6.01 ns 9.26 ns 3.25 ns 8.4(JIT有効) 2.74 ns 5.17 ns 2.43 ns
  7. 28 バージョンによる変化 ⚫ PHP 5.6 → 8.4(JIT) で差分は約3分の1に ⚫ しかし

    ゼロにはならない ⚫ JITを有効にしても差は残る
  8. 29 hash_equalsとの比較(PHP 8.4 JIT) ケース === hash_equals 長さ不一致 3.15 ns

    14.13 ns 1文字目不一致 2.74 ns 93.69 ns 中間不一致 3.63 ns 94.16 ns 最後不一致 5.17 ns 94.28 ns 完全一致 5.17 ns 94.52 ns
  9. 30 注目ポイント === の列 ⚫ 1文字目不一致 : 2.71ns ⚫ 最後不一致

    : 5.13ns ⚫ 約2.4ns の差がある ⚫ 「どこまで一致しているか」が   処理時間に現れている hash_equals の列 ⚫ 長さ一致ケースは全て 約94ns で安定 ⚫ これが 定数時間比較 の効果 ⚫ オーバーヘッドは約 90ns   (処理時間は無視できるレベル)
  10. 35 実装のキモ — XOR演算(^) 同じ文字 A ^ A = 0

    ビットが全て同じ → 0 違う文字 A ^ B ≠ 0 異なるビットがある →非0
  11. 39 なぜ長さのリークは許容されるのか? “ In general, it's not possible to prevent

    length leaks. So it's OK to leak the length. The important part is that it doesn't leak information about the difference of the two strings. CSRFトークンやHMACの出力長は 公開情報(固定長) 攻撃者が知りたいのは 内容 であり、長さだけでは内容を推測できない hash_equals も長さが異なると即座に false を返すが、問題ないか? — ircmaxell's Blog "It's All About Time"
  12. 41 RFC: timing_attack ⚫ 2013年12月: Rouven Weßling氏がRFCを提出 ⚫ RFCの名前が「timing_attack」 ⚫

    投票結果: 賛成22、反対1 で可決 — Request for Comments: Timing attack safe string comparison function
  13. 42 PHP 5.6(2014年8月) ⚫ タイミング攻撃対策として 初登場 ⚫ ext/hash に実装 導入の背景

    PHP 5.5の password_verify は定数時間比較を備えていたがパスワード検証専用。 CSRFトークンやHMAC署名など 文字列同士の汎用的な定数時間比較 が必要だった
  14. 47 迷ったときの判断基準 ⚫ 何か悪用できる → hash_equals ⚫ 特に問題ない → ===

    hash_equalsのオーバーヘッドは数十ナノ秒程度。 迷ったら hash_equals を選んでおけば安全 「この値が攻撃者に 1文字ずつ 漏れたら何が起きるか?」
  15. 54 今日のポイント ⚫ === は 早期リターン により処理時間が入力に依存する ⚫ hash_equals は

    全文字を必ず比較 して定数時間を実現 ⚫ 長さのリーク は許容される(内容の推測には使えない) ⚫ 使い分けの基準は「秘密情報をユーザー入力と照合しているか」 ⚫ 引数の順序は 既知の秘密が第1引数、ユーザー入力が第2引数 ⚫ パスワード検証は password_verify() を使う
  16. 55 多層防御の重要性 “ From a practical standpoint, I wouldn't worry

    about timing attacks until I was confident that the other potential vectors are secured. ⚫ Rate Limiting: 攻撃に必要な数万〜数十万の試行を制限 ⚫ トークンのローテーション: 有効期限を短くして試行回数を制限 ⚫ 他の対策も重要: SQLi、XSS対策が優先 — ircmaxell's Blog "It's All About Time" 「実用的には、他の脆弱性対策ができてからタイミング攻撃を心配すべき」
  17. 57 参考資料 ⚫ PHP RFC: timing_attack ⚫ PHP Manual: hash_equals

    ⚫ Linux man page: memcmp ⚫ ircmaxell's Blog ⚫ Remote Timing Attacks are Practical (PDF) - Rice大学の論文 ⚫ 本発表の元記事