Как правильно хранить пароли пользователей?
- Зачем нужна соль?
- Почему нельзя хранить пароли в SHA-256?
- Что такое bcrypt и argon2?
Не хранить пароль вовсе — только результат медленной функции с солью: argon2id, bcrypt или scrypt. При входе считаем то же самое и сравниваем.
- Соль — случайное значение для каждого пользователя: одинаковые пароли дают разные хеши, радужные таблицы бесполезны
- SHA-256 слишком быстрый: видеокарта перебирает миллиарды вариантов в секунду
- Параметры стоимости подбирают так, чтобы одна проверка шла ~0,1–0,5 секунды
А ещё Плюс: ограничение попыток, проверка по базам утёкших паролей, двухфакторная аутентификация
Подробно
Базы утекают — значит, нужно, чтобы по утёкшим данным нельзя было быстро восстановить пароли (люди используют их на других сайтах).
Почему не шифрование. Зашифрованный пароль расшифровывается ключом, а ключ лежит рядом с базой. Сравнивать можно и без исходного пароля.
Почему не обычный хеш. Хеш необратим, но быстрый: SHA-256 на видеокарте — миллиарды вычислений в секунду. Короткие и словарные пароли перебираются за минуты. Без соли ещё хуже: одинаковые пароли дают одинаковые хеши, и есть готовые радужные таблицы — заранее посчитанные хеши популярных паролей.
Как правильно:
- соль — случайные 16+ байт на каждого пользователя, хранится рядом с хешем. Атакующему приходится перебирать каждого отдельно;
- медленная функция — специально дорогая по времени и памяти:
- argon2id — современная рекомендация, настраивается память, итерации и параллелизм; память мешает перебору на видеокартах;
- bcrypt — проверенный временем, параметр cost (каждая единица удваивает время), ограничение длины пароля 72 байта;
- scrypt, PBKDF2 (если нужна сертификация FIPS, с большим числом итераций);
- параметры стоимости хранятся в строке хеша (
$argon2id$v=19$m=65536,t=3, p=4$соль$хеш) — можно повышать со временем и перехешировать при следующем входе; - перец (pepper) — дополнительный секрет вне базы (в HSM или KMS), по желанию.
Вокруг хранения: ограничение числа попыток входа и задержки, одинаковое сообщение «неверный логин или пароль», сравнение за постоянное время (библиотеки делают сами), проверка новых паролей по базе утёкших (Have I Been Pwned), минимальная длина важнее правил «цифра и спецсимвол», двухфакторная аутентификация, а лучше — passkeys.
Пример
from argon2 import PasswordHasher
ph = PasswordHasher() # argon2id с разумными параметрами
stored = ph.hash("correct horse battery staple")
# '$argon2id$v=19$m=65536,t=3,p=4$Z3Vp…$Xh9…' — соль и параметры внутри строки
ph.verify(stored, "correct horse battery staple") # True, иначе исключение
if ph.check_needs_rehash(stored):
stored = ph.hash(password) # параметры усилились — перехешироватьЧто хотят услышать
- Хранить не пароль, а медленный хеш с солью.
- Зачем соль и почему SHA-256 не подходит.
- Названия: argon2id, bcrypt, scrypt.
- Меры вокруг: лимиты попыток, 2FA, утёкшие пароли — плюс.
Частые ошибки
- Одна соль на всех пользователей.
- «Шифруем пароли AES» или «хешируем MD5».
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Хранить SHA-256 от пароля: хеш необратим, а соль нужна только если пароли короче восьми символов
- ✗Шифровать пароли AES и хранить ключ рядом
- ✗Хранить пароли открытым текстом в защищённой базе
- ✓Хранить результат медленной функции с солью — argon2id, bcrypt или scrypt
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.