Bcrypt в PHP: как хэшировать пароли быстро и без ошибок

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Bcrypt в PHP: как хэшировать пароли быстро и без ошибок

Вы когда-нибудь задумывались, почему ваш сайт на PHP тормозит при регистрации пользователей? Или, что ещё хуже, почему хакеры всё равно умудряются украсть пароли из вашей базы данных? Ответ может крыться в том, как вы используете bcrypt для хэширования паролей в PHP.

Этот вопрос — один из самых популярных на Stack Overflow (тысячи просмотров!). И неудивительно: безопасность паролей — это та тема, где одна ошибка может стоить вам репутации, а пользователям — их данных. Но сегодня мы не просто разберём, как использовать password_hash с bcrypt. Мы поговорим об оптимизации и производительности. Потому что, согласитесь, ждать 5 секунд при логине — это не круто.

Давайте разберёмся, как сделать хэширование паролей в PHP не только безопасным, но и быстрым. Поехали!

Почему bcrypt? И почему не MD5 или SHA?

Многие новички (и, увы, некоторые «бывалые») до сих пор используют md5() или sha1(). Это как закрывать дверь на пластиковую защёлку — вроде бы закрыто, но достаточно дунуть, и она откроется. MD5 и SHA — это быстрые хэши. Слишком быстрые. Хакер с современным GPU может перебрать миллиарды таких хэшей в секунду.

Bcrypt — это принципиально другой подход. Он специально спроектирован медленным. Он использует «соль» (случайные данные) и «cost factor» (фактор стоимости), который заставляет алгоритм выполняться дольше. Пока хакер будет считать один bcrypt-хэш, он мог бы перебрать миллион MD5. Вот почему bcrypt — это стандарт де-факто для PHP.

Основы: как использовать bcrypt в PHP правильно

Начиная с PHP 5.5, у нас есть волшебная функция password_hash(). Забудьте про ручное добавление соли и вызов crypt() — всё уже сделано за вас.

Вот как выглядит правильное хэширование пароля с bcrypt:

<?php
// Хэшируем пароль
$password = 'MySuperSecretPassword123!';
$hash = password_hash($password, PASSWORD_BCRYPT);

echo $hash;
// Вывод: $2y$10$... (строка длиной 60 символов)
?>

Всё! password_hash сам генерирует случайную соль и выбирает оптимальный cost factor (по умолчанию 10). А как проверить пароль при входе?

<?php
// Проверяем пароль
$inputPassword = 'MySuperSecretPassword123!';
$hashFromDB = '$2y$10$...'; // тот самый хэш из базы

if (password_verify($inputPassword, $hashFromDB)) {
    echo "Пароль верный! Добро пожаловать.";
} else {
    echo "Неверный пароль. Попробуйте ещё раз.";
}
?>

Просто, правда? Но дьявол, как всегда, в деталях. Давайте поговорим о самом важном — о производительности.

Оптимизация bcrypt: как найти баланс между безопасностью и скоростью

Главный параметр, который влияет на производительность bcrypt в PHP — это cost factor (стоимость). Он задаётся вторым аргументом в password_hash() как массив ['cost' => 12].

Что такое cost? Это логарифмическая шкала. Каждая единица увеличивает время выполнения примерно в 2 раза.

  • Cost = 10 (по умолчанию): ~0.1-0.2 секунды. Быстро, но для 2024 года уже маловато.
  • Cost = 12: ~0.4-0.8 секунды. Хороший баланс для большинства проектов.
  • Cost = 14: ~1.5-3 секунды. Очень безопасно, но пользователи начнут нервничать.
  • Cost = 16+: >5 секунд. Только если вы шпион или храните ядерные коды.

Как выбрать идеальный cost? Есть простое правило: на вашем сервере хэширование одного пароля должно занимать не более 250-500 миллисекунд. Это незаметно для пользователя, но делает атаку перебором слишком дорогой для хакера.

Тестируем производительность: реальный код

Давайте напишем скрипт, который поможет вам подобрать оптимальный cost для вашего сервера. Запустите его на своём хостинге или локальной машине.

<?php
/**
 * Функция для тестирования производительности bcrypt
 */
function testBcryptCost($targetTime = 0.5) {
    $cost = 8;
    echo "Тестирование bcrypt для целевого времени: " . $targetTime . " секунд\n\n";
    
    do {
        $start = microtime(true);
        
        // Хэшируем тестовый пароль
        $hash = password_hash('test_password', PASSWORD_BCRYPT, ['cost' => $cost]);
        
        $end = microtime(true);
        $time = $end - $start;
        
        echo "Cost: " . $cost . " | Время: " . round($time, 4) . " сек.\n";
        
        if ($time < $targetTime) {
            $cost++;
        } else {
            break;
        }
    } while ($cost <= 20);
    
    echo "\nРекомендуемый cost: " . ($cost - 1) . "\n";
    echo "Ожидаемое время хэширования: ~" . round($time, 4) . " сек.\n";
}

// Запускаем тест
testBcryptCost(0.5);
?>

Этот код автоматически подберёт для вас cost, при котором хэширование будет занимать ~0.5 секунды. Запустите его и запишите результат. Для большинства современных серверов это будет cost = 11 или 12.

Продвинутые техники: кэширование и асинхронность

Что делать, если ваш сайт посещают тысячи пользователей в минуту, и каждый должен проверить пароль? Лобовое решение — увеличить cost — может убить сервер. Вот несколько трюков:

1. Используйте кэш для частых проверок

Если пользователь вводит пароль несколько раз подряд (ошибся, потом исправился), вы можете временно кэшировать результат проверки в Redis или Memcached. Но будьте осторожны: никогда не кэшируйте успешные проверки надолго (максимум 5 минут), иначе при смене пароля злоумышленник может использовать старый кэш.

2. Асинхронное хэширование при регистрации

Регистрация нового пользователя — идеальное место для более дорогого cost (14-15). Вы можете отправить задачу на хэширование в очередь (RabbitMQ, Redis Queue) и не заставлять пользователя ждать. Проверка пароля при входе будет использовать меньший cost (10-11) для скорости.

3. «Гиб

Блоги

Рекомендаций по книгам