Пароль пользователя нельзя хранить в открытом виде и нельзя «шифровать» обратимым алгоритмом. База может утечь, и тогда все пароли окажутся под угрозой. Правильный подход — хранить необратимый хеш, по которому можно только проверить введённый пароль.

Почему не md5 и не sha1

Быстрые хеши вроде md5 и sha1 для паролей не подходят. Они проектировались для проверки целостности, а не для защиты секретов: современная видеокарта перебирает миллиарды вариантов в секунду. Соль спасает от радужных таблиц, но не от перебора.

bcrypt намеренно медленный. Внутри есть соль, а стоимость вычисления задаётся параметром cost. Увеличивая cost на единицу, вы примерно вдвое увеличиваете время проверки — и для пользователя, и для атакующего.

Как это выглядит в Laravel

Создание хеша при регистрации или смене пароля:

use Illuminate\Support\Facades\Hash;

$user = User::create([
    'name' => $request->name,
    'email' => $request->email,
    'password' => Hash::make($request->password),
]);

Проверка при входе:

if (! Hash::check($request->password, $user->password)) {
    throw ValidationException::withMessages([
        'email' => 'Неверный пароль.',
    ]);
}

В колонке password лежит строка вида $2y$12$…. В ней зашиты алгоритм, cost и соль. Отдельное поле для соли не нужно.

Cost и производительность

По умолчанию Laravel использует bcrypt с cost 12. На обычном сервере одна проверка занимает десятки миллисекунд — это нормально. Если поставить cost 14–15, вход станет заметно медленнее, а нагрузка на CPU вырастет. Для большинства проектов 12 — разумный баланс.

Менять cost можно через BCRYPT_ROUNDS в .env или в config/hashing.php. Старые хеши продолжат работать: cost хранится внутри строки, а Laravel умеет перехешировать пароль при успешном входе, если параметры изменились.

Хеш вне Laravel

Иногда нужно создать пользователя напрямую в базе или подготовить тестовые данные. Хеш можно сгенерировать в браузере: генератор паролей и bcrypt-хешей считает bcrypt локально и отдаёт строку $2y$12$…, совместимую с Hash::check() и password_verify().

Пароль при этом не отправляется на сервер. В базу записывайте только хеш; сам пароль сохраните в менеджере паролей.

Типичные ошибки

  • Двойное хеширование. Hash::make() при каждом сохранении модели превратит уже готовый хеш в новый — вход перестанет работать.
  • Сравнение строк вручную. Используйте Hash::check(), а не ===.
  • Слишком низкий cost. cost 4–6 проверяется почти мгновенно и плохо защищает.
  • Ограничение длины пароля. bcrypt принимает длинные строки, но некоторые старые обёртки обрезают их до 72 байт. Не ставьте лимит в 8 символов «для безопасности».
  • Логирование пароля. Убедитесь, что пароль не попадает в логи запросов и в трекер ошибок.

Что хранить и как проверять

В таблице пользователей достаточно одного поля password. Не храните рядом «контрольную сумму» или «подсказку». Для сброса используйте одноразовые токены с коротким сроком жизни, а не отправку старого пароля.

Если проект допускает смену алгоритма, например на Argon2id, Laravel поддерживает его через HASH_DRIVER=argon2id. Проверка остаётся прежней: Hash::check() сам определит алгоритм по строке.

Коротко

  • храните только bcrypt-хеш, никогда — пароль;
  • cost 12 — рабочий вариант по умолчанию;
  • проверяйте через Hash::check();
  • хеш можно создать локально в браузере и вставить в базу;
  • не логируйте пароли и не хешируйте их повторно.