Avatar ·

Как правильно использовать bcrypt для хеширования паролей в PHP?

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

Пробовал использовать password_hash() и password_verify(), но не уверен, что делаю всё правильно. Вот пример моего кода:

<?php
// Регистрация
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_DEFAULT);
// Сохраняю $hash в базу данных...

// Авторизация
$input_password = $_POST['password'];
$stored_hash = // получаю из базы
if (password_verify($input_password, $stored_hash)) {
    echo "Успешно!";
} else {
    echo "Неверный пароль";
}
?>

У меня несколько вопросов:

  1. Достаточно ли просто использовать PASSWORD_DEFAULT или нужно явно указывать PASSWORD_BCRYPT?
  2. Как настроить "стоимость" (cost factor) bcrypt? Я слышал, что это влияет на безопасность.
  3. Нужно ли добавлять "соль" (salt) вручную? Или PHP делает это автоматически?
  4. Как часто нужно обновлять хеши? И что делать, если алгоритм устареет?
  5. Есть ли какие-то подводные камни при работе с bcrypt в PHP?

Буду благодарен за подробное объяснение с примерами. Заранее спасибо!

<<>>

Подробное руководство по bcrypt в PHP

Вы на правильном пути! Использование password_hash() и password_verify() — это современный и безопасный подход. Давайте разберём все ваши вопросы по порядку.

1. PASSWORD_DEFAULT vs PASSWORD_BCRYPT

На текущий момент PASSWORD_DEFAULT использует именно bcrypt (с PHP 5.5.0). Однако в будущих версиях PHP алгоритм может измениться на более мощный. Если вы хотите быть уверены, что используете bcrypt всегда, указывайте PASSWORD_BCRYPT явно:

$hash = password_hash($password, PASSWORD_BCRYPT);

2. Настройка "стоимости" (cost factor)

Cost factor определяет, сколько времени будет потрачено на вычисление хеша. Чем выше значение, тем сложнее взломать хеш методом перебора. Значение по умолчанию — 10. Для современных серверов рекомендуется 12-14. Настраивается через третий параметр — массив опций:

$options = [
    'cost' => 12
];
$hash = password_hash($password, PASSWORD_BCRYPT, $options);

Важно: Слишком высокий cost (например, 16+) может создать нагрузку на сервер. Оптимальное значение — то, при котором хеширование занимает 0.1-0.5 секунды. Протестируйте на своём сервере:

$time_start = microtime(true);
password_hash('test', PASSWORD_BCRYPT, ['cost' => 12]);
$time_end = microtime(true);
echo "Время: " . ($time_end - $time_start) . " сек";

3. Соль (salt) — автоматически

PHP автоматически генерирует криптографически безопасную случайную соль для каждого хеша. Вам не нужно добавлять её вручную. Соль хранится внутри самого хеша, поэтому password_verify() может её извлечь и использовать для проверки. Никогда не пытайтесь задать свою соль — это снизит безопасность.

4. Обновление хешей при смене алгоритма

PHP предоставляет функцию password_needs_rehash(), которая проверяет, соответствует ли хеш текущим настройкам. Это полезно, когда вы повышаете cost или меняете алгоритм. Реализуйте проверку при авторизации:

if (password_verify($password, $hash)) {
    if (password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 12])) {
        $newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
        // Обновите хеш в базе данных
    }
    // Продолжайте авторизацию
}

5. Подводные камни и best practices

  • Длина пароля: bcrypt ограничен 72 символами. Более длинные пароли будут обрезаться. Если нужны пароли длиннее, сначала пропустите их через обычный хеш (например, SHA256), а затем через bcrypt. Но для большинства случаев 72 символов достаточно.
  • Кодировка: bcrypt работает с байтами, поэтому убедитесь, что используете одинаковую кодировку (UTF-8) при регистрации и авторизации.
  • Никогда не выводите хеш пользователю: Хеш — это внутренняя информация. Не показывайте его в логах, ошибках или на экране.
  • Используйте HTTPS: Хеширование защищает пароль в базе данных, но не при передаче. Всегда используйте HTTPS для защиты данных в пути.
  • Не используйте устаревшие функции: Избегайте md5(), sha1(), crypt() без bcrypt, а также собственных алгоритмов хеширования.

Полный пример класса для работы с паролями

<?php
class PasswordManager {
    private $algo = PASSWORD_BCRYPT;
    private $options = ['cost' => 12];

    public function hash($password) {
        return password_hash($password, $this->algo, $this->options);
    }

    public function verify($password, $hash) {
        return password_verify($password, $hash);
    }

    public function needsRehash($hash) {
        return password_needs_rehash($hash, $this->algo, $this->options);
    }
}

// Использование
$pm = new PasswordManager();
$hash = $pm->hash('user_password');
// Сохраните $hash в БД

// При авторизации
if ($pm->verify($input_password, $stored_hash)) {
    if ($pm->needsRehash($stored_hash)) {
        $newHash = $pm->hash($input_password);
        // Обновите запись в БД
    }
    echo "Добро пожаловать!";
}
?>

Этот подход гарантирует, что ваши пароли будут в безопасности, а система сможет адаптироваться к будущим улучшениям алгоритмов хеширования.

<<>>

Альтернативный подход: использование библиотеки для совместимости со старыми версиями PHP

Если ваш проект работает на PHP версии ниже 5.5.0, где нет встроенных функций password_hash() и password_verify(), вы можете использовать библиотеку password_compat от Anthony Ferrara (автора этих функций). Она добавляет обратную совместимость.

Установка через Composer:

composer require ircmaxell/password-compat

Пример использования (идентично нативным функциям):

<?php
require_once 'vendor/autoload.php';

// Хеширование
$hash = password_hash('mypassword', PASSWORD_BCRYPT, ['cost' => 10]);

// Проверка
if (password_verify('mypassword', $hash)) {
    echo 'Пароль верен!';
}
?>

Дополнительные советы для максимальной безопасности:

  • Используйте Bcrypt с солью: Даже если вы используете старую функцию crypt(), убедитесь, что соль генерируется случайно для каждого пользователя. Пример: $salt = '$2y$10$' . bin2hex(random_bytes(22));
  • Храните хеш в столбце типа VARCHAR(60) или CHAR(60): bcrypt хеш всегда имеет длину 60 символов. Если используете PASSWORD_DEFAULT, лучше выделить 255 символов на будущее.
  • Рассмотрите Argon2: Начиная с PHP 7.2, доступен алгоритм Argon2 (через PASSWORD_ARGON2I или PASSWORD_ARGON2ID). Он считается более безопасным, чем bcrypt. Однако bcrypt остаётся отличным выбором и поддерживается повсеместно.

Помните: безопасность паролей — это не разовая настройка, а постоянный процесс. Регулярно проверяйте, используете ли вы современные алгоритмы и обновляйте cost factor по мере роста производительности серверов.