Bcrypt в PHP: как хэшировать пароли правильно и быстро
Если вы когда-нибудь задавались вопросом "How do you use bcrypt for hashing passwords in PHP?", то вы не одиноки. Этот вопрос на Stack Overflow собрал тысячи просмотров — и не зря. Хэширование паролей — это база безопасности любого веб-приложения, и bcrypt остаётся одним из самых надёжных и популярных алгоритмов. В этой статье мы разберёмся, как использовать bcrypt в PHP, как настроить его для максимальной производительности и не попасть в ловушки.
Почему именно bcrypt? Потому что он устойчив к атакам перебором (brute-force) благодаря встроенной "соли" и регулируемой сложности. Но как сделать так, чтобы ваш сайт не тормозил при каждом логине? Об этом и поговорим.
Что такое bcrypt и почему он лучше MD5 или SHA1?
Давайте сразу расставим точки над i: никогда не используйте MD5 или SHA1 для хэширования паролей. Эти алгоритмы созданы для быстрого вычисления, а не для безопасности. Bcrypt, напротив, специально спроектирован медленным. Он использует cost factor (фактор стоимости), который определяет, сколько итераций хэширования будет выполнено. Чем выше cost factor, тем дольше вычисляется хэш — и тем сложнее злоумышленнику подобрать пароль.
В PHP работа с bcrypt максимально упрощена благодаря функциям password_hash() и password_verify(). Они появились в PHP 5.5 и с тех пор стали стандартом де-факто.
Как использовать bcrypt в PHP: базовый пример
Давайте начнём с простого. Вот как зарегистрировать пользователя и сохранить его пароль:
<?php
// Регистрация пользователя
$password = "MySuperP@ss123";
$hash = password_hash($password, PASSWORD_BCRYPT);
// Сохраняем $hash в базу данных (например, в поле password_hash)
echo $hash; // Выведет что-то вроде $2y$10$...
?>
А вот как проверить пароль при входе:
<?php
// Логин пользователя
$loginPassword = "MySuperP@ss123";
$storedHash = "$2y$10$..."; // из базы данных
if (password_verify($loginPassword, $storedHash)) {
echo "Пароль верный!";
} else {
echo "Неверный пароль.";
}
?>
Всё просто, правда? Но дьявол кроется в деталях. Давайте разберёмся с cost factor.
Настройка cost factor: баланс между безопасностью и скоростью
По умолчанию PASSWORD_BCRYPT использует cost factor = 10. Это означает 2^10 = 1024 итерации. На современном сервере это занимает примерно 0.1–0.2 секунды. Кажется, немного, но если у вас 1000 одновременных логинов — время начнёт расти.
Как выбрать оптимальный cost factor? Есть простое правило: он должен быть таким, чтобы хэширование одного пароля занимало не более 0.5 секунды на вашем сервере. Вы можете протестировать это с помощью функции password_hash() с разными значениями.
Вот пример теста производительности:
<?php
$password = "test_password";
$times = [];
for ($cost = 8; $cost <= 14; $cost++) {
$start = microtime(true);
password_hash($password, PASSWORD_BCRYPT, ['cost' => $cost]);
$times[$cost] = microtime(true) - $start;
}
print_r($times);
?>
На типичном shared-хостинге cost factor 10–12 будет оптимальным. Если у вас мощный сервер, можно поднять до 13–14. Но не перебарщивайте: слишком высокий cost factor может привести к тому, что ваш сервер будет тратить все ресурсы на хэширование при атаке.
Оптимизация производительности bcrypt в PHP
Теперь перейдём к самому интересному — как сделать bcrypt быстрее, не жертвуя безопасностью. Вот несколько проверенных стратегий:
1. Кэширование результатов
Если пользователь вводит правильный пароль, зачем хэшировать его снова при каждом запросе? Вы можете кэшировать результат на короткое время (например, на 5 минут) с помощью Redis или Memcached. Это особенно полезно для API с высокой нагрузкой.
<?php
$cacheKey = 'password_hash_' . md5($loginPassword);
$storedHash = $cache->get($cacheKey);
if (!$storedHash) {
$storedHash = password_hash($loginPassword, PASSWORD_BCRYPT, ['cost' => 12]);
$cache->set($cacheKey, $storedHash, 300); // 5 минут
}
?>
2. Используйте Argon2, если сервер поддерживает
PHP 7.2+ поддерживает Argon2 — более современный алгоритм, который устойчив к атакам на GPU. Он может быть быстрее bcrypt при том же уровне безопасности. Попробуйте:
<?php
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 1 << 17, // 128 MB
'time_cost' => 4,
'threads' => 2
]);
?>
Но помните: Argon2 требует больше памяти, что может быть проблемой на старом хостинге.
3. Оптимизация базы данных
Храните хэши в столбце типа VARCHAR(255) или CHAR(60) (для bcrypt). Индексируйте этот столбец, если вы ищете пользователя по паролю (хотя это плохая практика). Лучше используйте уникальный идентификатор пользователя.
4. Асинхронное хэширование
Если вам нужно хэшировать много паролей (например, при массовом импорте пользователей), используйте очереди задач (RabbitMQ, Gearman). Хэширование будет происходить в фоне, не блокируя основной поток.
<?php
// Отправляем задачу в очередь
$job = new HashPasswordJob($plainPassword);
dispatch($job);
// Воркер выполняет хэширование
$hash = password_hash($job->password, PASSWORD_BCRYPT, ['cost' => 12]);
// Сохраняем результат в БД
?>
Распространённые ошибки при использовании bcrypt в PHP
Даже опытные разработчики иногда допускают ошибки. Вот топ-5, которые стоит избегать:
- Слишком низкий cost factor — меньше 8 делает bcrypt бесполезным.
- Слишком высокий cost factor — больше 14 на слабом сервере может привести к таймаутам.
- Игнорирование соли —
password_hash()автоматически генерирует соль, не нужно делать это вручную. - Хранение пароля в plain text — никогда не логируйте и не выводите пароль.
- Использование старого PASSWORD_DEFAULT — он может измениться в будущих версиях PHP. Всегда указыва