# Хранение паролей (bcrypt, Argon2)

> Как сервер хранит пароли пользователей — не в открытом виде и не простым хешем, а медленной функцией с солью: Argon2id, scrypt, bcrypt, PBKDF2.

Источник: https://llmdocs.ru/wiki/password-storage/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Задача**: хранить проверочное значение, по которому нельзя быстро восстановить пароль
- **Рекомендуется**: Argon2id (RFC 9106)
- **Альтернативы**: scrypt, bcrypt, PBKDF2 (для FIPS)
- **Обязательно**: уникальная соль на каждый пароль
- **Нельзя**: открытый текст, обратимое шифрование, MD5/SHA-1/SHA-256 без растяжения
- **Стандарты**: NIST SP 800-63B, OWASP Password Storage Cheat Sheet
**Хранение паролей** — набор приёмов, с помощью которых сервер запоминает пароли пользователей так, чтобы проверять их при входе, но не иметь возможности (и не давать её взломщику) восстановить исходный пароль. Современная практика — хранить результат **медленной функции хеширования паролей** с уникальной солью: Argon2id, scrypt, bcrypt или PBKDF2.[1] Это часть [аутентификации](https://llmdocs.ru/wiki/authn-authz.md), и одна из самых частых ошибок в ней.

## Почему не просто хеш

Базы пользователей утекают регулярно. Если пароли лежат открытым текстом или зашифрованы обратимо (ключ обычно лежит рядом), утечка сразу раскрывает их все — а люди повторяют пароли на разных сайтах.

Обычная [криптографическая хеш-функция](https://llmdocs.ru/wiki/hash-function.md) (SHA-256, тем более MD5 или SHA-1) тоже не годится. Она спроектирована быть **быстрой**: современная видеокарта перебирает миллиарды SHA-256 в секунду, поэтому словарь из популярных паролей и их вариаций проверяется за минуты. Кроме того, одинаковые пароли дают одинаковые хеши, и заранее посчитанные таблицы (радужные таблицы) работают против всех пользователей сразу.

Поэтому функция для паролей должна обладать тремя свойствами:

1. **Соль** — случайная строка (16+ байт), уникальная для каждого пароля и хранимая рядом с хешем. Она делает одинаковые пароли разными и обесценивает заранее посчитанные таблицы.
2. **Растяжение** (work factor) — настраиваемая стоимость вычисления: проверка одного пароля занимает десятки–сотни миллисекунд вместо наносекунд.
3. **Затраты памяти** (memory-hardness) — функция требует много оперативной памяти, что мешает массовому параллельному перебору на GPU и ASIC.

## Алгоритмы

| Алгоритм | Год | Память | Комментарий |
|---|---|---|---|
| PBKDF2 | 2000 (RFC 2898) | нет | много итераций HMAC; нужен там, где требуется FIPS-140 |
| bcrypt | 1999[4] | 4 КБ | основан на шифре Blowfish, параметр cost; пароль обрезается до 72 байт |
| scrypt | 2009 (RFC 7914)[5] | да | первая популярная memory-hard функция |
| Argon2id | 2015 (RFC 9106)[2] | да | победитель Password Hashing Competition; рекомендация по умолчанию |

OWASP рекомендует Argon2id с параметрами не ниже 19 МиБ памяти, 2 итерации, 1 поток; при его отсутствии — scrypt, затем bcrypt с cost ≥ 10, а PBKDF2-HMAC-SHA256 — с 600 000 итераций.[1] Параметры подбирают так, чтобы проверка занимала заметное, но терпимое время на своём железе, и периодически повышают.

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

```text
$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
```

Благодаря этому параметры можно менять со временем: при следующем успешном входе пароль перехешируется с новыми настройками.

## Пример на Python

```python
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher()                # параметры по умолчанию — разумные
stored = ph.hash("correct horse battery staple")

def check(password: str, stored: str) -> bool:
    try:
        ph.verify(stored, password)
    except VerifyMismatchError:
        return False
    if ph.check_needs_rehash(stored):
        ...  # сохранить ph.hash(password) вместо старого значения
    return True
```

Библиотека argon2-cffi сама генерирует соль и сравнивает значения за постоянное время.[6] В других языках есть аналогичные готовые функции (`password_hash` в PHP, `golang.org/x/crypto/argon2` и `bcrypt` в Go, `passlib`/`bcrypt` в Python); писать свою схему не нужно.

## Перец и прочие детали

- **Перец** (pepper) — дополнительный секрет, общий для всех паролей и хранящийся отдельно от базы (в [хранилище секретов](https://llmdocs.ru/wiki/secrets-management.md) или HSM). Утечка одной базы без перца не позволяет даже начать перебор.
- Сравнивать хеши нужно функцией постоянного времени, чтобы не создавать утечку по времени ответа.
- Медленное хеширование делает форму входа удобной целью для перегрузки: нужны ограничения частоты запросов.
- Ограничение bcrypt в 72 байта иногда обходят предварительным хешированием пароля, но это надо делать аккуратно (в base64), иначе нулевые байты обрежут вход.

## Политика паролей

NIST SP 800-63B (редакция 4) пересмотрел привычные правила:[3] требовать длину не менее 8 символов (лучше 15 при однофакторном входе), разрешать длинные пароли (64+ символа) и любые символы Unicode, **не** требовать обязательной смены по расписанию и «состава» (заглавная, цифра, спецсимвол), зато проверять пароль по спискам утёкших и популярных. Лучшая защита от последствий утечек — [Двухфакторная аутентификация](https://llmdocs.ru/wiki/two-factor-auth.md) и passkeys, при которых сервер вообще не хранит общий секрет.

## См. также

- [Хеш-функция](https://llmdocs.ru/wiki/hash-function.md)
- [Аутентификация и авторизация](https://llmdocs.ru/wiki/authn-authz.md)
- [OWASP Top 10](https://llmdocs.ru/wiki/owasp-top-10.md)
- [Криптография с открытым ключом](https://llmdocs.ru/wiki/public-key-cryptography.md)

## Источники

1. [OWASP — Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
2. [RFC 9106 — Argon2 Memory-Hard Function for Password Hashing](https://www.rfc-editor.org/rfc/rfc9106)
3. [NIST SP 800-63B — Digital Identity Guidelines: Authentication](https://pages.nist.gov/800-63-4/sp800-63b.html)
4. [Provos, Mazières — A Future-Adaptable Password Scheme (bcrypt, USENIX 1999)](https://www.usenix.org/legacy/events/usenix99/provos/provos.pdf)
5. [RFC 7914 — The scrypt Password-Based Key Derivation Function](https://www.rfc-editor.org/rfc/rfc7914)
6. [Python — argon2-cffi documentation](https://argon2-cffi.readthedocs.io/)
