みなさん、こんにちは!日々の開発やインフラの管理、本当にお疲れ様です。
セキュリティの世界へようこそ!「パスワードはハッシュ化して保存しなさい」というのは、もうエンジニアの常識として耳にタコができるほど聞いていますよね。SHA-2やSHA-3といった強力なハッシュ関数を使って、パスワードを生で保存しないようにしているシステムは多いはずです。
でも、ちょっと待ってください。
「ハッシュ化したパスワードを安全に保存した」だけで、本当に安心していませんか?
実は、どれだけ強力なハッシュ関数を使っていても、「照合する瞬間」のプログラムの書き方一つで、いとも簡単にパスワードの壁を突破されてしまうおそろしい現実があるのです。今回は、攻撃者が密かに狙う「タイミング攻撃」という巧妙な手口と、それを防ぐための「定数時間比較(Constant-time comparison)」について、身近な例えを交えながら一歩ずつ学んでいきましょう!
—
1. 家の鍵と「電子錠」で考えるタイミング攻撃の正体
サイバー攻撃の手口を理解するために、ちょっと身近な「家の電子錠(暗証番号を入力するタイプの鍵)」を想像してみてください。
この電子錠、暗証番号が「4桁の数字」だとします。正しい番号は 1234 です。
泥棒がこの鍵をこっそり破ろうとするとき、ただやみくもにボタンを押すだけではありません。実は、電子錠の内部の動きをじーーっと観察しているんです。
普通のプログラム(何も対策をしていないコード)は、こんな風にパスワードをチェックしています。
1. 入力された1文字目の 1 と、正解の1文字目の 1 を比べる。合っている!
2. 入力された2文字目の 2 と、正解の2文字目の 2 を比べる。合っている!
3. 入力された3文字目の 5 と、正解の3文字目の 3 を比べる。間違っている! → 「違います!」と即座にエラーを返す。
お気づきでしょうか?
普通のプログラムは、「間違っている箇所を見つけた瞬間」に処理をストップして、「間違い!」と返答する仕組みになっています。
これが何を意味するかというと、「合っている文字が多いほど、正解にたどり着くまでの処理時間がコンマ数ミリ秒ほど長くなる」ということです。
泥棒は、この「ほんのわずかな時間のズレ」をストップウォッチで測ります。
- 「最初の文字が
1のときは、わずかに反応が遅いぞ。ということは、1文字目は1だ!」 - 「次の文字を
2にしたら、またわずかに遅くなった! 2文字目は2だ!」
このように、返答にかかる時間(タイムラグ)を手がかりにして、1文字ずつ正解を暴いていく攻撃。これが、まさに「タイミング攻撃(Timing Attack)」と呼ばれるものです。現実のネットワークの世界では、サーバーとの通信速度の揺らぎがあるため少しコツがいりますが、超高速なネットワークやローカル環境であれば、この方法でパスワードが丸裸にされてしまいます。
—
2. 泥棒を惑わせる!「定数時間比較」という魔法
「じゃあ、どうやってこの泥棒の盗み見を防げばいいの?」と思いますよね。
対策はとてもシンプルですが、人間の直感とは少し違うアプローチを取ります。それが「定数時間比較(Constant-time comparison)」です。
先ほどの電子錠の例で言うなら、泥棒がどんな間違い方をしようとも、「絶対に途中で諦めず、最後の最後まで確認してから、同じタイミングで結果を返す」というやり方です。
- 1文字目が違っていても、2文字目、3文字目、4文字目まですべて厳密に比べ続けます。
- その上で、「全部合っていたかどうか」の最終結果だけを最後に判定し、どれだけ時間がかかろうとも、不正解であっても正解であっても、処理にかかる時間が常に一定(定数時間)になるようにプログラムを組みます。
これなら、泥棒がストップウォッチをどれだけ睨みつけても、「どこが合っていて、どこが間違っているのか」のヒントが一切漏れません。泥棒は完全に手詰まりになりますよね。
—
3. 【実践】PHPで学ぶ!ダメなコード vs 安全なコード
それでは、実際のシステム開発の現場で、このタイミング攻撃をどう防ぐのかを見ていきましょう。今回は多くのWebシステムで使われているPHPを例に取ります。
❌ やってはいけない!タイミング攻撃に弱いコード
まずは、世の中の初心者がよく書いてしまいがちな「危ないコード」です。
<?php
// 【危険な実装例】
// == 演算子や strcmp関数を使ってはいけません!
function unsafe_password_check($user_input_hash, $stored_hash) {
// == や === 演算子は、左から順に文字を比べていき、
// 違った瞬間に処理を打ち切って false を返します。
// これがタイミング攻撃の隙を生んでしまいます。
if ($user_input_hash === $stored_hash) {
return true; // パスワード一致
}
return false; // パスワード不一致
}
?>
このコードの何がいけないかというと、=== 演算子は「高速化(最適化)」のために、不一致を見つけた瞬間に比較処理を終了してしまう点です。セキュリティの観点では、この「親切な高速化」が命取りになります。
⭕ 模範解答!安全な「定数時間比較」コード
では、プロのセキュリティエンジニアはどのように書くのでしょうか?
実は、現代の主要なプログラミング言語には、このタイミング攻撃を防ぐための専用の関数が最初から用意されています。PHPの場合は hash_equals() という関数を使います。
<?php
/**
* 【安全な実装例】
* データベースから取り出したハッシュと、ユーザーが入力したパスワードのハッシュを比較する
*/
function secure_password_check($input_password, $stored_hash) {
// 1. まず、入力されたパスワードを安全なアルゴリズム(例: PASSWORD_DEFAULT)でハッシュ化します
// ※今回は説明のため、すでにハッシュ化された文字列を受け取る想定にしています
$input_hash = hash('sha256', $input_password);
// 2. hash_equals() を使って安全に比較します
// この関数は、中身が一致していようがしていまいが、
// 常に「一定の時間がかかる」ように内部で設計されています。
if (hash_equals($stored_hash, $input_hash)) {
return true; // 認証成功!
}
return false; // 認証失敗
}
?>
たったこれだけです! === の代わりに hash_equals() を使うだけで、あなたのアプリケーションはタイミング攻撃というプロ顔負けの手口からしっかりと守られます。
—
4. 他の言語での定数時間比較のやり方
PHP以外の言語を使っている方のために、主要な言語での安全な比較方法もサクッとご紹介しておきますね。実務の参考にしてください!
- Node.js (JavaScript)
- 標準の
===ではなく、組み込みモジュールであるcryptoのtimingSafeEqual()を使います。
const crypto = require('crypto');
function safeCompare(userHash, storedHash) {
// バッファの長官が同じであることを確認してから比較します
const buf1 = Buffer.from(userHash);
const buf2 = Buffer.from(storedHash);
if (buf1.length !== buf2.length) {
return false;
}
return crypto.timingSafeEqual(buf1, buf2);
}
- Python
- 標準ライブラリの
hmacモジュールに、定数時間比較を行う関数が用意されています。
import hmac
def safe_compare(user_hash, stored_hash):
# hmac.compare_digest を使うことでタイミング攻撃を防ぎます
return hmac.compare_digest(user_hash, stored_hash)
どの言語であっても、「文字列の単純な比較(== や ===)」は認証トークンやパスワードのハッシュ比較には使わず、「定数時間比較を行う専用の関数」を探して使うのが鉄則です。
—
まとめ:一歩ずつ、セキュアなエンジニアへ
今回は、ハッシュ関数の先にある「タイミング攻撃」の仕組みと、それを防ぐ「定数時間比較」について解説しました。
- パスワードの比較で「違った瞬間に処理を終える」と、時間のズレから答えを盗み見られる。
- これを防ぐためには、どんな場合でも同じ時間がかかる「定数時間比較」を使う必要がある。
- PHPなら
hash_equals()、Pythonならhmac.compare_digest()などの専用関数を活用しよう。
セキュリティの対策と聞くと難しく身構えてしまうかもしれませんが、こうして一つひとつの意味を紐解いていくと、「なぜこの関数を使わなければならないのか」の理由がすっきりと見えてきますよね。
今日の学びを、ぜひ明日からのコーディングに活かしてみてください。あなたの書くコードが、より安全で信頼されるものになることを応援しています!それではまた次のセキュリティの教室でお会いしましょう!
コメント