«Как именно хранить пароли пользователей в базе данных?» — каждый создатель однажды натыкается на этот вопрос. Ответ ясен. Вот безопасный способ их хранить, по порядку, без шагов атаки.
Почему открытый текст, шифрование и сырые хеши все проваливаются
Предположим, что база данных однажды утечёт. Когда это случится, ущерб сильно различается по методу хранения.
- Открытый текст: каждый пароль раскрыт в тот же миг, как он утёк. Хуже того, переиспользование паролей сцепляет утечку с другими сервисами пользователя. Худший случай.
- Шифрование (обратимое): ключ возвращает всё назад — поэтому если ключ утечёт вместе с ним, вы снова в открытом тексте. Пароль никогда не нужно читать обратно, так что обратимость ничего вам не даёт.
- Сырой хеш (MD5/SHA-256): быстрый хеш позволяет атакующему быстро проверять догадки. Распространённые пароли падают перед радужными таблицами и перебором.
Четыре стадии к безопасному хранению
Быстрее всего понять это как добавление по одному исправлению к слабому методу.
Ключевое понимание: соль и медленный хеш делают разную работу. Соль побеждает предвычисление (радужные таблицы) и массовый взлом за счёт переиспользования. Медленный хеш роняет перебор до непрактичной скорости. Вам нужны оба, прежде чем это станет безопасным (→ что такое хеширование).
На практике: что делать сейчас
Используйте Argon2id (или bcrypt)
Для новых систем сделайте Argon2id первым выбором — он может требовать ещё и память, что сопротивляется перебору на GPU. Если вы предпочитаете хорошо проверенную реализацию, bcrypt на практике более чем достаточно. Используйте реализацию из стандартной библиотеки как есть для любого из них.
Пусть соль генерирует библиотека
bcrypt/Argon2 генерируют соль для каждого пользователя и встраивают её внутрь строки хеша. Вы не держите отдельный столбец для соли. Самодельный MD5(salt + password) — классика того, чего делать не надо.
Настройте стоимость под вашу среду
Задайте фактор стоимости bcrypt или память/итерации/параллелизм Argon2 настолько высоко, насколько можно, пока легитимный вход всё ещё ощущается мгновенным. Серверы становятся быстрее — но и атакующие тоже; пересматривайте и повышайте это ежегодно.
Проверяйте стандартной функцией сравнения
При входе сопоставляйте ввод с сохранённым хешем функцией проверки из библиотеки (большинство используют безопасное по времени сравнение с постоянным временем). Не пишите своё сравнение строк.
Переносите слабые хеши перехешированием при входе
Если вы уже храните MD5/SHA-256, в тот миг, когда пользователь успешно входит, перехешируйте новой схемой и сохраните. Тех, кто не входил, можно пока усилить, повторно захешировав существующий хеш с помощью bcrypt (слоёный хеш).
Типичные ошибки vs правильная реализация
Типичная ошибка
- Шифровать пароли для хранения (ключ утёк → конец)
- Хранить
MD5(password)илиSHA-256(password)напрямую - Использовать одну общую соль для всех пользователей
- Самодельный
hash(salt + password)
Правильная реализация
- Односторонний хеш с Argon2id / bcrypt
- Соль для каждого пользователя (библиотека добавляет её автоматически)
- Стоимость настолько высока, насколько комфортно, повышается периодически
- Использовать стандартные функции хеширования/проверки как есть
Хеширование, шифрование и кодирование
Эти три понятия путают, но они полностью различаются тем, можно ли их обратить. Для хранения паролей допустимо только хеширование.
| Хеширование | Шифрование | Кодирование (Base64 и т. п.) | |
|---|---|---|---|
| Обратимо? | Нет (одностороннее) | Да, с ключом | Да, кем угодно |
| Ключ | Нет | Нужен | Нет |
| Типичное применение | Хранение паролей, обнаружение подмены | Конфиденциальность данных или трафика | Изменение формата данных |
| Для хранения паролей | Использовать (соль + медленный хеш) | Не использовать | Не использовать (то же, что открытый текст) |
Рекомендуемые настройки (OWASP)
Минимальные настройки из OWASP Password Storage Cheat Sheet. Считайте их нижней границей и повышайте под свой сервер.
| Алгоритм | Минимальная настройка | Когда использовать |
|---|---|---|
| Argon2id | 19 МиБ памяти, 2 итерации, параллелизм 1 | Первый выбор для новых систем |
| scrypt | N=2^17 (128 МиБ), r=8, p=1 | Когда Argon2id недоступен |
| bcrypt | Стоимость 10 или выше. Ввод ограничен 72 байтами | Существующие системы, проверенная репутация |
| PBKDF2-HMAC-SHA256 | 600 000 итераций или больше | Когда требуется соответствие FIPS 140 |
Проверьте также встроенные средства вашей платформы. password_hash() в PHP по умолчанию использует bcrypt (PASSWORD_DEFAULT), а в PHP 8.4 стоимость по умолчанию поднята с 10 до 12. Django по умолчанию использует PBKDF2; чтобы перейти на Argon2, установите django[argon2] и поставьте Argon2 первым в PASSWORD_HASHERS. Django автоматически перехеширует пароли, сохранённые старым алгоритмом, когда пользователь входит.
Как проверить свой сервис
Схему обычно можно определить по началу сохранённой строки хеша. Посмотрите несколько строк в среде, которую вы контролируете, не копируя данные продакшена в другие места.
| Как выглядит хеш | Схема | Что делать |
|---|---|---|
$argon2id$v=19$m=…,t=…,p=…$ | Argon2id | Проверьте, что m, t и p не ниже минимума OWASP |
$2y$12$… / $2b$10$… | bcrypt (число — стоимость) | Повысьте, если ниже 10 |
pbkdf2_sha256$1000000$… | PBKDF2 (число — количество итераций) | Проверьте, что не меньше 600 000 |
| 32 шестнадцатеричных символа | Скорее всего MD5 | Переносите перехешированием при входе |
| 40 / 64 шестнадцатеричных символа | Скорее всего SHA-1 / SHA-256 | То же, что выше |
| Строка входа как есть | Открытый текст | Переносите в первую очередь. Оцените последствия на случай утечки |
Проверьте также следующее:
- Вводимые пароли не записываются в логи приложения или веб-сервера, отчёты об ошибках и инструменты аналитики
- Если используете bcrypt, вы решили, как обрабатывать ввод длиннее 72 байт
- Резервные копии и копии базы данных для тестирования содержат только хеши того же вида
Реальный пример: хранение в открытом виде расширило ущерб
В инциденте Takufile-bin (2019) несанкционированный доступ к серверу привёл к утечке около 4,8 миллиона записей, а пароли для входа среди них хранились в открытом виде. Если бы они хранились с солью и медленным хешем, риск того, что утёкшие пароли напрямую используют на других сервисах, был бы гораздо ниже. Даже если невозможно предотвратить любое вторжение, способ хранения паролей меняет масштаб ущерба от утечки.
Взгляд этого сайта: для хранения паролей используйте проверенную стандартную реализацию
Хранение паролей — это не место для творчества. Использовать широко проверенные стандартные реализации Argon2/bcrypt — самое безопасное и самое простое в эксплуатации. Наша позиция: не новаторствуйте здесь. Тратьте усилия на снижение зависимости от паролей вообще — на тщательную многофакторную аутентификацию (MFA) и со временем на переход к passkey (без пароля). Даже сильнейший хеш не спасёт слабый, переиспользованный пароль.
Читать дальше
- Глоссарий: что такое хеширование паролей / что такое соль
- Основы: правильное хранение паролей (со стороны пользователя) / выбор менеджера паролей
- Защита: правильный выбор MFA (снизить зависимость от паролей)
- Глоссарий: что такое passkey (движение к беспарольности)
- Кейс: утечка данных Takufile-bin (2019) (хранение паролей в открытом виде расширило ущерб)
Источники
- OWASP, «Password Storage Cheat Sheet»: cheatsheetseries.owasp.org
- NIST, «SP 800-63B-4» (август 2025 г.), хранение паролей (соль и хеширование): pages.nist.gov
- RFC 9106, «Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications»: rfc-editor.org
- Руководство PHP, «password_hash»: php.net
- Документация Django, «Password management in Django» (5.2): docs.djangoproject.com