Безопасное хеширование и соль для паролей в PHP: как правильно?
Правильное хеширование паролей в PHP
Вы абсолютно правы: md5() и статическая соль — это устаревший и опасный подход. Современный PHP предоставляет встроенные функции для безопасного хеширования, которые решают все ваши проблемы. Вот правильное решение.
Используйте password_hash() и password_verify()
Начиная с PHP 5.5, существуют две ключевые функции:
- password_hash() — создаёт хеш пароля с автоматической генерацией соли и использованием алгоритма bcrypt (по умолчанию).
- password_verify() — проверяет пароль против хеша.
Пример регистрации пользователя
<?php
// Получаем пароль из формы
$password = $_POST['password'];
// Хешируем пароль (соль генерируется автоматически)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Сохраняем $hashed_password в БД (например, в поле VARCHAR(255))
$sql = "INSERT INTO users (username, password_hash) VALUES (?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_POST['username'], $hashed_password]);
?>
Пример проверки пароля при логине
<?php
// Получаем пароль из формы
$password = $_POST['password'];
// Достаём хеш из БД по имени пользователя
$sql = "SELECT password_hash FROM users WHERE username = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
echo "Успешный вход!";
} else {
echo "Неверный логин или пароль";
}
?>
Почему это безопасно?
- Автоматическая соль: Каждый хеш содержит уникальную соль, сгенерированную криптографически стойким генератором. Соль хранится прямо в строке хеша — вам не нужно отдельное поле в БД.
- bcrypt: Алгоритм медленный (намеренно), что делает атаки перебором практически невозможными. Одна итерация bcrypt занимает ~100-200 мс.
- Защита от timing-атак:
password_verify()использует константное время сравнения. - Будущая совместимость:
PASSWORD_DEFAULTавтоматически обновит алгоритм при выходе новых версий PHP.
Дополнительные настройки (опционально)
Вы можете увеличить стоимость (cost factor) для bcrypt, чтобы сделать хеширование ещё медленнее:
$options = ['cost' => 12]; // По умолчанию 10, можно 12-14 для большей безопасности
$hashed_password = password_hash($password, PASSWORD_BCRYPT, $options);
Важно: не ставьте слишком высокий cost — проверьте на своём сервере, чтобы время хеширования не превышало 0.5 секунды.
Что хранить в БД?
Вам нужно только одно поле VARCHAR(255) (bcrypt даёт строку длиной 60 символов, но лучше взять с запасом). Соль и алгоритм уже встроены в эту строку. Пример хеша:
$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
Здесь $2y$10$ — это идентификатор алгоритма и стоимость, а остальное — соль (22 символа) и хеш.
Итог
Забудьте про md5(), sha1() и ручную соль. Используйте только password_hash() и password_verify() — это стандарт безопасности в PHP. Ваш код станет проще, а пароли — защищёнными.
Дополнительные меры безопасности
Коллега верно описал основной подход. Я добавлю несколько важных моментов, которые помогут ещё больше усилить защиту.
1. Проверка на утечки (Have I Been Pwned)
Перед хешированием полезно проверить, не был ли пароль скомпрометирован. Можно использовать API от Have I Been Pwned (k-anonymity):
<?php
function is_password_pwned(string $password): bool {
$hash = strtoupper(sha1($password));
$prefix = substr($hash, 0, 5);
$suffix = substr($hash, 5);
$response = file_get_contents("https://api.pwnedpasswords.com/range/{$prefix}");
return strpos($response, $suffix) !== false;
}
if (is_password_pwned($password)) {
echo "Этот пароль был скомпрометирован, выберите другой.";
}
?>
2. Ограничение попыток входа
Даже с bcrypt стоит защищаться от брутфорса. Реализуйте задержку после нескольких неудачных попыток:
- Храните в БД количество неудачных попыток и время последней.
- После 5 ошибок блокируйте вход на 15 минут.
- Используйте CAPTCHA после 3 неудач.
3. Двухфакторная аутентификация (2FA)
Для критичных приложений добавьте 2FA через TOTP (Google Authenticator). Библиотеки: spomky-labs/otphp или sonata-project/google-authenticator.
4. Обновление хешей
Если в будущем вы измените алгоритм или стоимость, можно автоматически обновлять хеши при входе пользователя:
if (password_verify($password, $user['hash'])) {
if (password_needs_rehash($user['hash'], PASSWORD_DEFAULT, ['cost' => 12])) {
$newHash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]);
// обновить в БД
}
}
Помните: безопасность — это многослойная защита. Хеширование — только первый слой, но крайне важный. Удачи в разработке!