【PHP8.x】hash_equals関数の使い方

作成日: 更新日:

hash_equals関数は、2つの文字列を、一定の時間で比較することによって、タイミング攻撃を防ぐ関数です。この関数は、特にセキュリティが求められる場面で、ユーザーが提供した情報とサーバーが保持する機密情報(例えばパスワードのハッシュ値や認証トークンなど)を安全に照合するために設計されています。

一般的な文字列比較演算子(==)は、文字列の先頭から文字を順に比較し、異なる文字が見つかった時点で比較を終了します。そのため、文字列がどの程度一致しているかによって比較にかかる時間が変動してしまいます。攻撃者はこの時間の差を分析することで、機密情報の部分的な内容を推測しようとすることが可能になります。このような攻撃をタイミング攻撃と呼びます。

hash_equals関数は、比較する2つの文字列の長さが異なる場合を除き、常に文字列全体を比較します。これにより、文字列が一致するかどうかにかかわらず、処理時間がほぼ一定となり、タイミング攻撃による情報漏洩のリスクを大幅に軽減します。関数は、比較する最初の文字列(known_string)と2番目の文字列(user_string)を引数として受け取ります。両方の文字列が同じ場合にtrueを返し、それ以外の場合はfalseを返します。セキュリティを強化するために、機密情報の比較にはこの関数を使用することが強く推奨されます。

基本的な使い方

構文(syntax)

<?php
$knownString = 'expected_secure_token';
$userString = 'user_submitted_token';

$areEqual = hash_equals($knownString, $userString);
?>

引数(parameters)

string $known_string, string $user_string

  • string $known_string: 比較対象となる既知の文字列
  • string $user_string: ユーザーからの入力などの比較対象文字列

戻り値(return)

bool

二つの文字列が等しい場合は TRUE を、そうでない場合は FALSE を返します。

サンプルコード

PHP hash_equalsで安全な文字列比較をする

<?php

/**
 * hash_equals関数の使い方をデモンストレーションします。
 *
 * hash_equalsは、文字列を時間固定で比較するために使用されます。
 * これは、パスワードのハッシュ値の比較などでタイミング攻撃を防ぐために重要です。
 *
 * @param string $userInput ユーザーから提供された文字列(例: フォーム入力)
 * @return void
 */
function verifySecret(string $userInput): void
{
    // アプリケーションが知っている秘密の文字列(例: データベースに保存されたパスワードのハッシュ値、APIキーなど)
    // 実際のアプリケーションでは、これは安全な方法で取得されます。
    $knownSecret = 'aSuperSecurePasswordHashValue123abc';

    // hash_equalsを使用して、時間固定で文字列を比較します。
    // これにより、文字列の比較にかかる時間が入力によって変化するのを防ぎ、
    // タイミング攻撃による秘密の推測を防ぎます。
    if (hash_equals($knownSecret, $userInput)) {
        echo "認証成功: 秘密の文字列が一致しました。\n";
    } else {
        echo "認証失敗: 秘密の文字列が一致しません。\n";
    }
}

// --- デモンストレーション ---

echo "--- 正しい入力を試行 ---\n";
verifySecret('aSuperSecurePasswordHashValue123abc');

echo "\n--- 間違った入力を試行 ---\n";
verifySecret('wrongPassword');

echo "\n--- 長さが異なる間違った入力を試行 ---\n";
verifySecret('aSuperSecurePasswordHashValue123ab'); // 短い文字列

?>

PHPのhash_equals関数は、2つの文字列を安全に比較するために使用されます。特に、パスワードのハッシュ値やAPIキーなどの機密情報を比較する際に、タイミング攻撃と呼ばれるセキュリティ上の脆弱性を防ぐことを目的としています。

この関数は、引数として$known_string(アプリケーションが知っている正しい秘密の文字列、例えばデータベースに保存されたパスワードのハッシュ値)と、$user_string(ユーザーが入力した文字列、例えばフォームから送信されたパスワード)の2つの文字列を受け取ります。

通常の文字列比較では、不一致が見つかった時点で比較を終了するため、文字列が長いほど、また一致する文字が多いほど比較にかかる時間が長くなることがあります。この時間の差を利用して、攻撃者が秘密の文字列を推測しようとするのがタイミング攻撃です。

hash_equals関数は、どのような場合でも常に同じ時間で2つの文字列全体を比較します。これにより、比較にかかる時間から文字列の内容を推測されるのを防ぎます。2つの文字列が完全に一致すればtrue(真)を返し、そうでなければfalse(偽)を返します。

サンプルコードでは、アプリケーションが持つ秘密の文字列とユーザーからの入力をhash_equalsで比較し、認証の成功・失敗を表示することで、この関数の安全な利用方法を示しています。

hash_equals関数は、パスワードのハッシュ値比較など、セキュリティが重要な場面で時間固定の文字列比較を行うための関数です。通常の==演算子と異なり、文字列の長短や不一致箇所に関わらず比較にかかる時間が一定になります。これにより、比較時間から秘密の情報が推測されるタイミング攻撃を防ぎ、システム全体の安全性を高めます。

引数には、第一引数にアプリケーションが知っている既知の安全な文字列(例: データベースに保存されたパスワードのハッシュ値)、第二引数にユーザーからの入力文字列を渡してください。戻り値は比較結果を示す真偽値です。サンプルコードの$knownSecretはあくまでデモンストレーション用の文字列であり、実際のシステムではパスワードを直接保存せず、password_hash()などで生成したハッシュ値を安全に保存し、認証時にはpassword_verify()関数と組み合わせて使用することが強く推奨される点にご注意ください。

PHP hash_equalsでタイミング攻撃を防ぐ

<?php

/**
 * ユーザーからの入力文字列とシステム内の既知の文字列を安全に比較します。
 *
 * この関数は、hash_equals を使用してタイミング攻撃のリスクを軽減し、
 * セキュリティを向上させた文字列比較の例を示します。
 *
 * hash_equals は、特にパスワードハッシュやAPIトークン、CSRFトークンなどの
 * 秘密の文字列を比較する際に非常に重要です。
 *
 * @param string $user_string ユーザーから提供された入力文字列(例: パスワードハッシュ、トークン)
 * @return bool 2つの文字列が一致すれば true、そうでなければ false
 */
function compareStringsSecurely(string $user_string): bool
{
    // システムに保存されている、攻撃者から隠すべき既知の文字列をシミュレートします。
    // 実際には、データベースや設定ファイルなどから安全に取得されるべきです。
    $known_string = '5f4dcc3b5aa765d61d8327deb882cf99'; // 例: "password" のMD5ハッシュ

    // ---------------------------------------------------------------------
    // PHPのデフォルトの厳密な比較演算子 (===) を使用した場合の脆弱性(exploit)について
    // ---------------------------------------------------------------------
    // $user_string === $known_string のような単純な比較は、
    // 2つの文字列の不一致が最初に見つかった時点で比較処理を中止します。
    // 例えば:
    // - "abcde" と "abxde" を比較する場合、'c'と'x'の時点で比較は終了します。
    // - "abcde" と "abcyz" を比較する場合、'd'と'y'の時点で比較は終了します。
    //
    // このように、一致する文字数が多いほど比較にかかる処理時間が長くなる傾向があります。
    // 攻撃者はこの「処理時間のわずかな差」を何度も計測・分析することで、
    // 秘密の $known_string の文字を先頭から一文字ずつ推測しようとする
    // 「タイミング攻撃(Timing Attack)」を実行する可能性があります。
    // これが「php hash_equals exploit」というキーワードで指されるセキュリティリスクの一例です。
    // ---------------------------------------------------------------------

    // ---------------------------------------------------------------------
    // 安全な比較: hash_equals を使用
    // ---------------------------------------------------------------------
    // hash_equals は、上記のようなタイミング攻撃を防ぐために特別に設計された関数です。
    //
    // 特徴:
    // 1. 常に文字列全体を比較します。
    // 2. 比較処理にかかる時間が、入力文字列の内容(どこで不一致が起きるか)に
    //    依存しないように設計されています。
    //
    // これにより、攻撃者が処理時間の差から秘密の文字列を推測することを極めて困難にします。
    //
    // 引数:
    // - $known_string: 既知の安全な文字列(システムが持つ秘密の値)
    // - $user_string: ユーザーから提供された文字列(攻撃対象となる可能性のある値)
    // 戻り値: bool - 両方の文字列が同じ場合に true、そうでなければ false
    if (hash_equals($known_string, $user_string)) {
        return true; // 文字列が一致し、安全に比較が完了
    } else {
        return false; // 文字列が一致しない
    }
}

// --- 関数の動作確認のための実行例 ---

// ケース1: 正しい文字列を送信した場合
$correctInput = '5f4dcc3b5aa765d61d8327deb882cf99'; // "password" のMD5ハッシュ
if (compareStringsSecurely($correctInput)) {
    echo "ケース1: 正しい入力 - 比較成功。\n";
} else {
    echo "ケース1: 正しい入力 - 比較失敗。(予期せぬエラー)\n";
}

// ケース2: 誤った文字列を送信した場合(最初の文字が異なる)
$wrongInput1 = 'xefdcc3b5aa765d61d8327deb882cf99';
if (compareStringsSecurely($wrongInput1)) {
    echo "ケース2: 誤った入力1 - 比較成功。(予期せぬエラー)\n";
} else {
    echo "ケース2: 誤った入力1 - 比較失敗。\n";
}

// ケース3: 誤った文字列を送信した場合(途中の文字が異なる)
$wrongInput2 = '5f4dcc3b5aa765d61d8327deb882cfx0';
if (compareStringsSecurely($wrongInput2)) {
    echo "ケース3: 誤った入力2 - 比較成功。(予期せぬエラー)\n";
} else {
    echo "ケース3: 誤った入力2 - 比較失敗。\n";
}

// ケース4: 長さの異なる文字列を送信した場合
$shortInput = '5f4dcc3b5aa765d61d8327deb882cf';
if (compareStringsSecurely($shortInput)) {
    echo "ケース4: 短い入力 - 比較成功。(予期せぬエラー)\n";
} else {
    echo "ケース4: 短い入力 - 比較失敗。\n";
}

?>

PHP 8.4のhash_equals関数は、二つの文字列を安全に比較するために使用されます。特に、パスワードハッシュやAPIトークン、CSRFトークンなど、攻撃者から秘密にすべき文字列を比較する際に非常に重要な役割を果たします。

一般的な比較演算子(===など)では、二つの文字列が不一致になった時点で比較処理を中止するため、一致する文字が多いほど処理時間が長くなる傾向があります。攻撃者はこの処理時間のわずかな差を何度も測定・分析することで、秘密の文字列を一文字ずつ推測しようとする「タイミング攻撃」を仕掛ける可能性があります。これが「php hash_equals exploit」というキーワードで指されるセキュリティリスクの一例です。

hash_equals関数は、このようなタイミング攻撃を防ぐために特別に設計されています。この関数は、常に文字列全体を比較し、比較にかかる時間が入力文字列の内容(どこで不一致が起きるか)に依存しないように設計されています。これにより、攻撃者が処理時間の差から秘密の文字列を推測することを極めて困難にします。

引数には、システムが持つ既知の秘密の文字列($known_string)と、ユーザーから提供された比較対象の文字列($user_string)を渡します。戻り値はブール型で、二つの文字列が完全に一致すればtrue、そうでなければfalseを返します。サンプルコードのcompareStringsSecurely関数は、このhash_equalsを利用して、ユーザーからの入力とシステム内の既知の文字列を安全に比較する具体的な方法を示しています。

hash_equals関数は、パスワードハッシュやAPIトークンなど、システム内の秘密の文字列とユーザーからの入力を安全に比較するための必須機能です。通常の厳密な比較(===)は、文字列の不一致が最初に見つかった時点で比較を中断するため、処理時間のわずかな差から秘密の文字列を推測する「タイミング攻撃」を受ける危険性があります。hash_equalsは、このようなセキュリティリスクを回避するため、常に文字列全体を一定時間で比較するように設計されています。これにより、攻撃者が比較時間から情報を得ることを極めて困難にします。サンプルコードでは$known_stringが直接記述されていますが、実際のシステムではデータベースなどから安全に取得し、第一引数に「システムが持つ秘密の文字列」、第二引数に「ユーザーからの入力文字列」を渡して利用してください。

【PHP8.x】hash_equals関数の使い方 | いっしー@Webエンジニア