Hash equals: примеры (PHP)
hash_equals(string $known_string, string $user_string): boolФункция hash_equals в PHP
Функция hash_equals осуществляет безопасное сравнение двух строк, защищенное от атак по времени. Она сравнивает строки, используя алгоритм постоянного времени выполнения, что делает невозможным определение различий между строками на основе времени выполнения операции.
Эта функция применяется в криптографических целях, таких как сравнение хэшей, токенов аутентификации, кодов подтверждения и других чувствительных данных, где утечка информации через побочные каналы (как время выполнения) может стать уязвимостью.
hash_equals(string $known_string, string $user_string): bool
- $known_string — известная строка, с которой производится сравнение. Обычно это эталонное значение, хранящееся в системе (например, хэш пароля из базы данных).
- $user_string — строка, предоставленная пользователем (например, хэш введенного пароля).
Функция возвращает true, если строки идентичны, и false в противном случае. Важно, чтобы обе строки были одинаковой длины, иначе функция немедленно вернет false, не производя сравнения.
Примеры использования hash_equals
<?php
$known = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8';
$user = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8';
if (hash_equals($known, $user)) {
echo 'Строки идентичны';
} else {
echo 'Строки различаются';
}
?>Строки идентичны
<?php
$token = 'abc123';
$input = 'xyz789';
var_dump(hash_equals($token, $input));
?>bool(false)
<?php
$hash1 = 'longhash';
$hash2 = 'short';
var_dump(hash_equals($hash1, $hash2));
?>bool(false)
Альтернативные функции в PHP
В PHP не существует прямых функциональных аналогов hash_equals, обеспечивающих сравнение с постоянным временем. Однако для сравнения строк могут применяться другие функции, не безопасные по времени:
- Оператор === (строгое сравнение) — сравнивает строки, но время выполнения зависит от различий в строках, что создает уязвимости. Использовать для криптографических данных не рекомендуется.
- strcmp — функция бинарно-безопасного сравнения строк. Возвращает 0 при равенстве, но также подвержена атакам по времени. Основное применение — общее сравнение строк без требований к безопасности.
Предпочтение hash_equals отдается во всех сценариях, связанных с проверкой секретных данных: токенов сессий, хэшей паролей (в сочетании с password_verify), подписей и т.д.
Аналоги функции в других языках
Модуль hmac содержит функцию compare_digest, которая также обеспечивает безопасное сравнение.
import hmac
known = b"secret_value"
user = b"secret_value"
result = hmac.compare_digest(known, user)
print(result)True
В криптографическом модуле есть функция timingSafeEqual.
const crypto = require('crypto');
const known = Buffer.from('secret');
const user = Buffer.from('secret');
try {
console.log(crypto.timingSafeEqual(known, user));
} catch (err) {
console.log('Buffers must have the same length');
}true
Прямого аналога не существует. Обычное сравнение через = или LIKE не безопасно по времени. Рекомендуется выполнять сравнение в прикладном коде, используя безопасные функции языка программирования.
Типичные ошибки
Начиная с PHP 8, функция выбрасывает TypeError, если аргументы не являются строками.
<?php
hash_equals(true, 'string');
?>TypeError: hash_equals(): Argument #1 ($known_string) must be of type string, bool given
Путаница между известной и пользовательской строкой может привести к логическим ошибкам, хотя синтаксически это корректно.
<?php
$storedHash = 'hash_from_db';
$inputHash = 'user_input';
// Неправильно: порядок обратный
if (hash_equals($inputHash, $storedHash)) {
// Условие может не выполниться, если строки разные
}
?>Рекомендуется придерживаться соглашения: первый аргумент — известное значение, второй — предоставленное пользователем.
Изменения в PHP 8
В PHP 8.0 функция hash_equals стала строже типизированной. Если передается аргумент, не являющийся строкой, генерируется исключение TypeError. В предыдущих версиях PHP функция пыталась преобразовать аргументы в строки, что могло приводить к неожиданному поведению.
В PHP 8.1 и 8.2 существенных изменений в поведении функции не было. Функция остается стабильным и рекомендуемым инструментом для безопасного сравнения строк.
Расширенные примеры применения
<?php
// Симулируем хэш, хранящийся в базе данных
$storedPasswordHash = password_hash('my_password', PASSWORD_DEFAULT);
// Пользователь вводит пароль
$userInput = 'my_password';
// Сначала проверяем пароль через password_verify, который тоже безопасен по времени
if (password_verify($userInput, $storedPasswordHash)) {
echo 'Пароль верный';
} else {
echo 'Неверный пароль';
}
?>Пароль верный
<?php
session_start();
// Генерация токена и сохранение в сессии
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Проверка токена из формы
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$userToken = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $userToken)) {
die('Ошибка проверки CSRF токена');
}
// Токен верный, продолжаем обработку
}
?>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<!-- другие поля формы -->
</form><?php
$secretKey = 'your_secret_key';
$message = 'Важное сообщение';
$correctSignature = hash_hmac('sha256', $message, $secretKey);
// Подпись, полученная от клиента
$receivedSignature = 'некоторая_подпись';
if (hash_equals($correctSignature, $receivedSignature)) {
echo 'Подпись действительна';
} else {
echo 'Подпись недействительна';
}
?>Подпись недействительна
<?php
$validApiKey = 'abc123def456';
$userApiKey = $_SERVER['HTTP_API_KEY'] ?? '';
// Безопасное сравнение, даже если ключи разной длины
if (hash_equals($validApiKey, $userApiKey)) {
http_response_code(200);
echo json_encode(['status' => 'success']);
} else {
http_response_code(403);
echo json_encode(['status' => 'invalid key']);
}
?>