Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Хеширование паролей: argon2 и bcrypt

Хеширование паролей: почему MD5 нельзя и как настроить argon2

Хеширование паролей: argon2id, bcrypt и PBKDF2, соль, параметры стоимости, проверка и миграция со старых схем без сброса паролей.

Прямой ответ

Пароли нельзя хранить ни в открытом виде, ни в виде обычного хеша. Обычные хеш-функции (MD5, SHA-1, SHA-256) созданы для скорости: современная видеокарта считает миллиарды значений в секунду, поэтому база хешей перебирается по словарю за минуты. Для паролей нужны медленные адаптивные функции с солью и настраиваемой стоимостью.

Актуальные варианты:

  • Argon2id — победитель Password Hashing Competition, рекомендованный OWASP. Устойчив и к GPU, и к атакам с использованием памяти (memory-hard).
  • bcrypt — проверенная временем схема на основе Blowfish; широко поддержана, но ограничена 72 байтами пароля и менее устойчива к аппаратному ускорению.
  • scrypt — memory-hard функция, доступна в OpenSSL и библиотеках; рабочий вариант, если Argon2 недоступен.
  • PBKDF2 — доступен везде, но требует очень большого числа итераций, чтобы приблизиться к устойчивости memory-hard схем.

Недопустимо: MD5, SHA-1, SHA-256 без растягивания, «двойной MD5», самописные схемы.

Обязательные свойства:

  1. Соль уникальна для каждого пароля. Общая соль позволяет атаковать всю базу одним словарём. Соль генерируется криптографически случайной и хранится вместе с хешем.
  2. Параметры стоимости задаются явно. Для Argon2id это объём памяти, число итераций и степень параллелизма; для bcrypt — cost factor.
  3. Формат хранения самодокументируемый. Строка вида $argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash> содержит алгоритм и параметры, поэтому проверка не зависит от настроек по умолчанию в коде.
  4. Сравнение без утечки по времени. Используются функции постоянного времени, а не ==.

Ориентиры по параметрам (OWASP): для Argon2id — 19 МБ памяти, t=2, p=1 как минимум; при возможности 46 МБ, t=1, p=1. Точные значения подбирают так, чтобы одна проверка занимала порядка 100–500 мс на вашем железе.

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)

stored = ph.hash("correct horse battery staple")   # сохраняем в БД
try:
    ph.verify(stored, user_input)
    if ph.check_needs_rehash(stored):
        stored = ph.hash(user_input)               # обновляем параметры при входе
except VerifyMismatchError:
    raise PermissionError("неверный пароль")

Практика: настройка и миграция

Подбор параметров под своё железо. Измеряйте время одной проверки: слишком быстро — снизится стоимость перебора для атакующего, слишком медленно — пользователи будут ждать, а сервер упрётся в CPU при всплеске входов. Практика: цель 200–300 мс и ограничение числа одновременных проверок.

Миграция со старой схемы без сброса паролей. Если в базе MD5, нельзя «перехешировать» пароли: исходный пароль неизвестен. Рабочая схема:

  1. Оборачиваем старый хеш: при входе вычисляем argon2id(старый_md5_хеш + соль) и сохраняем в новом поле, помечая запись как мигрированную.
  2. При следующем успешном входе получаем исходный пароль и заменяем обёртку на полноценный argon2id(пароль).
  3. Через разумный срок (6–12 месяцев) неактивные записи принудительно сбрасываем.
ALTER TABLE users
  ADD COLUMN password_hash_v2 TEXT,
  ADD COLUMN password_scheme  TEXT NOT NULL DEFAULT 'md5_legacy';

Рекомендации:

  1. Не ограничивайте длину пароля сверху без причины. Ограничение в 32 символа — наследие старых схем; для argon2 достаточно защититься от DoS лимитом в несколько килобайт.
  2. Проверяйте пароль по спискам скомпрометированных. Словарная проверка при регистрации ловит больше слабых паролей, чем правила «обязательна цифра и спецсимвол».
  3. Ограничивайте число попыток входа. Без этого стоимость перебора для атакующего определяется не хешем, а скоростью отправки запросов.
  4. Хеширование — не шифрование. Если данные должны быть обратимы, это другая задача — см. шифрование данных в покое.

Практический сценарий: утечка базы и разные последствия

Контекст: два сервиса потеряли дамп таблицы пользователей. В одном пароли хранились как SHA-256 с общей солью, в другом — argon2id с индивидуальной солью.

  1. Первый сервис. Дамп расшифрован по радужным таблицам и словарю за несколько часов: 68 % паролей совпали с распространёнными, часть оказалась одинаковой у разных пользователей — общая соль это не скрывала.
  2. Второй сервис. Подбор одного пароля занимает сотни миллисекунд и требует значительной памяти; массовый перебор непрактичен. Пострадали только самые слабые пароли, попавшие в публичные списки.
  3. Действия. Первый сервис принудительно сбросил пароли и уведомил пользователей; второй ограничился уведомлением и рекомендацией сменить пароль, добавив проверку по списку скомпрометированных.
  4. Проверка. В обоих случаях проверили, не использовались ли пароли повторно в других системах (credential stuffing): добавили ограничение попыток и алерт на всплеск неудачных входов.
  5. Профилактика. Тест в CI проверяет, что схема хеширования и параметры соответствуют политике; отклонение ломает сборку.

Вывод: выбор функции хеширования определяет стоимость атаки после утечки. Это одна из немногих настроек безопасности, которую невозможно исправить задним числом без принудительного сброса паролей.

FAQ

Почему нельзя использовать SHA-256 с большим числом итераций? Можно, это PBKDF2-HMAC-SHA256, и он лучше «чистого» SHA-256. Но он не является memory-hard, поэтому ASIC и GPU дают атакующему большое преимущество. Argon2id и scrypt требуют значительной памяти на каждую проверку, что уравнивает шансы.

Что делать с bcrypt и паролями длиннее 72 байт? Bcrypt молча игнорирует всё после 72-го байта. Если длинные пароли допустимы, либо выбирают Argon2, либо предварительно хешируют вход SHA-256 с последующим bcrypt — но это добавляет нюансов, поэтому проще взять Argon2id.

Нужна ли «перечница» (pepper) поверх соли? Pepper — секрет, хранящийся вне базы (в KMS или переменных окружения). Он повышает стоимость атаки при утечке только базы. Это разумная дополнительная мера, но она не заменяет корректную функцию хеширования и усложняет ротацию секрета.

Как часто менять параметры хеширования? По мере роста мощности железа — раз в несколько лет. Механизм check_needs_rehash позволяет обновлять хеши при входе пользователей без принудительного сброса; хранение параметров внутри строки хеша делает такую миграцию прозрачной.

Источники: OWASP Password Storage Cheat Sheet, RFC 9106 (Argon2), PostgreSQL pgcrypto — ссылки в шапке.