「ハッシュ検証」で何を確かめたいのか
まず、「ハッシュ検証」という言葉のイメージから整理します。
ハッシュ検証とは、ざっくり言うと、
「今手元にある“生の値”が、保存されている“ハッシュ値”と本当に対応しているかを確かめること」
です。
トークン
API キー
パスワード
ファイル内容
など、「元の値は保存したくない/できないけれど、同じかどうかは判定したい」ものに対して使います。
ここで大事なのは、
「ハッシュ値をそのまま文字列比較するだけでは不十分な場面がある」
「用途によって、使う関数が違う」
この 2 点です。
ここから、「トークン系のハッシュ検証」と「パスワード用ハッシュ検証」に分けて見ていきます。
基本形:SHA-256 などの「自前ハッシュ」の検証
まずはシンプルな検証の流れを押さえる
例えば、ワンタイムトークンを SHA-256 でハッシュ化して保存しているケースを考えます。
保存時はこうでした。
$token = generate_one_time_token(32); // 生トークン
$tokenHash = hash('sha256', $token);
// DB には $tokenHash だけを保存
PHP検証時にやりたいことは、とてもシンプルです。
「クライアントから送られてきたトークンを同じルールでハッシュして、保存済みハッシュと一致するかを見る」
コードで書くと、こうなります。
$inputToken = $_GET['token'] ?? '';
$inputHash = hash('sha256', $inputToken);
// DB から取り出した保存済みハッシュ
$storedHash = $row['token_hash'];
if ($inputHash === $storedHash) {
// OK
} else {
// NG
}
PHPこれが一番素朴な「ハッシュ検証」です。
ただし、ここには一つ重要な落とし穴があります。
重要ポイント:比較には hash_equals() を使う
ハッシュ値の比較には、=== ではなく hash_equals() を使うのが基本です。
if (!hash_equals($storedHash, $inputHash)) {
// NG
}
PHP理由は「タイミング攻撃(タイミングサイドチャネル)」を避けるためです。
=== で比較すると、「どこまで一致しているか」によって比較にかかる時間が微妙に変わる可能性があります。
攻撃者がこの時間差を大量に計測すると、「先頭何文字が合っているか」を推測できてしまうことがあります。
hash_equals() は、「文字列の長さが同じか」「中身が同じか」を、時間差が出にくい方法で比較してくれます。
トークンや API キーなど、「推測されたら困る値」のハッシュ検証では、
必ず hash_equals() を使う、と覚えておいてください。
salt 付きハッシュの検証
保存時の復習
前にやった「salt 付きハッシュ」の例を思い出します。
保存時は、こんな流れでした。
$value = 'some-secret-value';
$salt = generate_salt(); // ランダムな salt
$hash = hash('sha256', $value . $salt); // salt 付きハッシュ
// DB には salt と hash の両方を保存
PHP検証時の流れ
検証時にやることは、次の 2 ステップです。
- DB から「salt と保存済みハッシュ」を取り出す
- 入力値と salt を同じルールでハッシュして、保存済みハッシュと比較する
コードで書くと、こうなります。
$inputValue = $_POST['value'] ?? '';
// DB から salt と hash を取得
$salt = $row['salt'];
$storedHash = $row['hash'];
// 入力値+salt を同じルールでハッシュ
$inputHash = hash('sha256', $inputValue . $salt);
// 比較は hash_equals で
if (!hash_equals($storedHash, $inputHash)) {
// NG
} else {
// OK
}
PHPここでの重要ポイントは、
「保存されているのは“ハッシュ値と salt”だけで、元の値はどこにも残っていない」
「検証時は、必ず“同じルール”でハッシュを再計算して比較する」
という 2 点です。
ルール(結合順序、アルゴリズムなど)が少しでも違うと、
当然ハッシュ値は一致しません。
パスワード用ハッシュの検証は「password_verify 一択」
ここだけは別枠で考える
パスワードだけは、「自前のハッシュ検証」をしてはいけません。
やってはいけない例は、こんな感じです。
// ダメな例
$storedHash = $row['password_hash'];
$inputHash = hash('sha256', $password);
if ($storedHash === $inputHash) {
// OK
}
PHPパスワードの保存・検証には、
必ず password_hash() と password_verify() を使います。
正しい検証の流れ
保存時はこうでした。
$hash = password_hash($password, PASSWORD_DEFAULT);
// DB に $hash を保存
PHP検証時は、こうです。
$storedHash = $row['password_hash'];
if (!password_verify($password, $storedHash)) {
// パスワードが違う
} else {
// OK
}
PHPここでの重要ポイントは、
「自分でハッシュを再計算しない」
「アルゴリズムや salt、コストは、全部ハッシュ文字列の中に埋め込まれている」
ということです。
password_verify() は、保存済みハッシュの中身を読み取って、
正しい方法で検証してくれます。
実務での「ハッシュ検証」ユーティリティの形
トークン検証用の小さな関数
トークンや API キーなど、「SHA-256 でハッシュ化して保存している値」を検証する関数は、こんな形にできます。
/**
* 生トークンと保存済み SHA-256 ハッシュを検証する
*/
function verify_sha256_hash(string $value, string $storedHash): bool
{
$inputHash = hash('sha256', $value);
return hash_equals($storedHash, $inputHash);
}
PHPsalt 付きなら、こうです。
/**
* 生の値と salt 付き SHA-256 ハッシュを検証する
*/
function verify_salted_sha256(string $value, string $salt, string $storedHash): bool
{
$inputHash = hash('sha256', $value . $salt);
return hash_equals($storedHash, $inputHash);
}
PHP使い方のイメージは、こんな感じです。
if (!verify_sha256_hash($inputToken, $row['token_hash'])) {
// NG
}
if (!verify_salted_sha256($inputApiKey, $row['salt'], $row['hash'])) {
// NG
}
PHPパスワード検証用の関数
パスワード用は、もっとシンプルです。
function verify_password(string $password, string $storedHash): bool
{
return password_verify($password, $storedHash);
}
PHPログイン処理の中では、こう使えます。
if (!verify_password($password, $user['password_hash'])) {
// 認証失敗
}
PHPまとめ:今日からの「ハッシュ検証」ユーティリティ
大事なポイントをコンパクトにまとめると、こうなります。
ハッシュ検証とは、「生の値を同じルールでハッシュして、保存済みハッシュと比較する」こと。
トークンや API キーなどの検証では、hash() で再計算し、比較には必ず hash_equals() を使う。
salt 付きハッシュでは、「保存済みの salt を取り出して、入力値+salt を同じルールでハッシュして比較」する。
パスワードだけは別枠で、password_hash() と password_verify() を使い、自前のハッシュ検証はしない。
実務でまず用意しておくと便利な「検証ユーティリティ」は、この 3 本です。
function verify_sha256_hash(string $value, string $storedHash): bool
{
$inputHash = hash('sha256', $value);
return hash_equals($storedHash, $inputHash);
}
function verify_salted_sha256(string $value, string $salt, string $storedHash): bool
{
$inputHash = hash('sha256', $value . $salt);
return hash_equals($storedHash, $inputHash);
}
function verify_password(string $password, string $storedHash): bool
{
return password_verify($password, $storedHash);
}
PHPもし、あなたのコードの中で「ハッシュ値を === で比較している」「パスワードを SHA-256 で検証している」ような箇所があれば、そこがこのユーティリティに差し替えるべきポイントです。
その一箇所を直すだけで、「ハッシュの扱い方」が一段階プロっぽくなります。
