Перейти к содержанию
Innovialabs

Как правильно хранить пароли: bcrypt, scrypt и Argon2

Практика4 мин

Коротко

Пароли хранят только как медленный хеш с солью — Argon2id, scrypt или bcrypt, — а не как MD5 или SHA. Старые хеши переводят на новый алгоритм постепенно, при входе пользователя.

Короткий ответ

Пароль никогда не хранят в открытом виде и не шифруют. Его прогоняют через специальную функцию хеширования паролей — Argon2id, scrypt или bcrypt — и хранят результат. При входе пароль хешируют заново и сравнивают.

Для нового проекта выбирайте Argon2id. Если в вашем стеке его нет, подойдут scrypt или bcrypt. MD5, SHA-1 и даже SHA-256 «как есть» для паролей не годятся.

Почему MD5 и SHA не работают

MD5 и SHA создавались для проверки целостности данных, и главное их свойство — скорость. Для паролей это минус: если база утекла, злоумышленник перебирает варианты на видеокартах с огромной скоростью.

Что идёт не так с «простым» хешем:

  • Одинаковые пароли дают одинаковые хеши. Видно, у кого пароль совпадает, и один подобранный пароль раскрывает всех.
  • Радужные таблицы. Для популярных паролей хеши давно посчитаны заранее, подбор сводится к поиску в таблице.
  • Быстрый перебор. Словари и правила мутаций («Password1!», «qwerty2024») проверяются за минуты.

Двойной хеш (md5(sha1(p))) или «секретная» соль в коде проблему не решают — скорость остаётся прежней.

Что делают соль и work factor

Соль — случайная строка, уникальная для каждого пароля. Она хранится рядом с хешем и не является секретом. Соль делает хеши одинаковых паролей разными и обесценивает радужные таблицы.

Work factor (стоимость) — параметр, который делает вычисление хеша намеренно медленным. Для пользователя разница незаметна, а для атакующего каждая попытка становится дорогой.

Современные библиотеки генерируют соль сами и записывают её вместе с параметрами прямо в строку хеша, например $argon2id$v=19$m=19456,t=2,p=1$.... Отдельная колонка под соль не нужна.

Сравнение алгоритмов

АлгоритмЧто настраиваетсяОсобенности
Argon2idпамять, итерации, параллелизмУстойчив к перебору на GPU за счёт расхода памяти. Выбор по умолчанию
scryptN (память и время), r, pТоже требует много памяти, хорошо поддерживается
bcryptcost (логарифм числа раундов)Проверен временем, есть почти везде. Учитывает только первые 72 байта пароля
PBKDF2число итерацийНужен, если требуется соответствие FIPS. Нагружает только CPU

Как выбрать параметры

  1. Начните с рекомендаций OWASP Password Storage Cheat Sheet — там указаны актуальные минимальные значения для каждого алгоритма.
  2. Измерьте время хеширования на своём продакшен-сервере. Ориентир — доли секунды на одну операцию, чтобы вход оставался быстрым, а сервер выдерживал пиковую нагрузку.
  3. Учитывайте память: Argon2 с большим параметром памяти при множестве одновременных входов может исчерпать RAM.
  4. Пересматривайте параметры при смене железа и повышайте их со временем.

Пример на Python с библиотекой argon2-cffi:

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher()  # безопасные параметры по умолчанию

stored = ph.hash("user-password")

try:
    ph.verify(stored, "user-password")
    if ph.check_needs_rehash(stored):
        stored = ph.hash("user-password")  # параметры устарели — пересохраняем
except VerifyMismatchError:
    pass  # неверный пароль

В Node.js с bcrypt: await bcrypt.hash(password, 12) и await bcrypt.compare(password, hash). В PHP — password_hash(), password_verify() и password_needs_rehash().

Миграция старых хешей при входе

Пароли пользователей вы не знаете, поэтому пересчитать всю базу сразу нельзя. Рабочая схема:

  1. Добавьте поле с алгоритмом хеша (или определяйте его по префиксу строки).
  2. При входе проверьте пароль старым способом.
  3. Если пароль верный — сразу посчитайте хеш Argon2id и перезапишите запись.
  4. Пользователей, которые долго не входят, можно защитить сразу: обернуть старый хеш в новый (argon2(md5(password))), а при следующем входе заменить на чистый Argon2id.
  5. Через разумный срок оставшиеся старые хеши удалите и попросите таких пользователей сбросить пароль.

Частые ошибки

  • Сравнение хешей обычным == вместо функции библиотеки (возможна атака по времени).
  • Слишком низкий cost «потому что тормозит» — лучше ограничить частоту попыток входа.
  • Логирование паролей в запросах и ошибках.
  • Обрезание пароля или запрет длинных паролей и спецсимволов.
  • Отсутствие rate limiting и 2FA — даже идеальный хеш не спасает от подбора через форму входа.

Частые вопросы

Нет. Шифрование обратимо: если украдут ключ вместе с базой, все пароли раскроются. Хеш необратим, и для проверки пароля исходник не нужен.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.