Алексей Меленчук
вся записка
дата
объём 5 мин чтения

Как хранить пароли: хеш — это не шифрование

Чем хеширование отличается от шифрования, почему SHA-256 для паролей не годится, зачем нужна соль и какие алгоритмы и параметры рекомендует OWASP в 2026 году.

Коротко. Пароль не шифруют — его хешируют функцией, которая намеренно медленная. Главный параметр здесь не название алгоритма, а стоимость одного вычисления: она и есть защита.

Разговор почти всегда начинается с одной фразы: «пароли у нас в базе зашифрованы». Дальше выясняется, что лежит sha256 от пароля, а иногда и вовсе md5. Путаница между шифрованием и хешированием — не терминологическая придирка, из неё растут все остальные ошибки.

Разница, из которой всё следует

Шифрование обратимо. У него есть ключ, и по ключу текст восстанавливается. Значит, где-то должен лежать ключ — и тот, кто добрался до базы, чаще всего доберётся и до него. Про то, почему переменная окружения секрет не прячет, есть отдельный разбор.

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

Пароль хранить незачем. Нужна только возможность сказать «да, это он».

Почему SHA-256 здесь плохой выбор

Тот же аргумент, что делает SHA-256 хорошей функцией для контрольных сумм, делает её негодной для паролей: она быстрая. Считать её умеет видеокарта, и умеет миллиардами в секунду.

Нападающий, унёсший базу, не разгадывает хеш. Он перебирает пароли: берёт список из утечек, считает хеш каждого и сравнивает. Вся защита сводится к тому, сколько стоит одно вычисление. У SHA-256 оно стоит ничтожно мало, и база на миллион строк разбирается на ноутбуке.

Поэтому для паролей берут функции, которые намеренно медленные и настраиваемо медленные. Это не побочный эффект, а их назначение.

Соль

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

Она закрывает две вещи. Во-первых, заранее посчитанные таблицы: таблица под соль x7q… бесполезна для соли k2m…, а считать таблицу под каждого пользователя — то же самое, что перебирать напрямую. Во-вторых, одинаковые пароли перестают давать одинаковые хеши, и по базе больше не видно, что три сотни человек выбрали одно и то же.

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

Что брать в 2026 году

Памятка OWASP по хранению паролей ставит первым Argon2id — минимум 19 МиБ памяти, две итерации, одна степень параллелизма. Есть и вариант с меньшим расходом процессора: 46 МиБ, одна итерация.

Дальше по списку scrypt, затем bcrypt, и PBKDF2 — когда требуется сертификация по FIPS. Для bcrypt памятка называет фактор стоимости от 10 и выше.

Отличие Argon2id и scrypt от bcrypt существенное: они требовательны не только к процессору, но и к памяти. Специализированное железо, на котором перебор дёшев, упирается именно в память — её на кристалле много не разместишь.

Ограничение bcrypt, о котором забывают: он принимает 72 байта. Всё, что длиннее, молча отбрасывается. Парольная фраза из десяти слов превращается в свои первые 72 байта, и пользователь об этом не узнает. Тихое усечение — худший вид дефекта: он не проявляется до разбора утечки.

Как уходить со старого алгоритма

Пересчитать хеши разом нельзя: исходных паролей у вас нет, в том и смысл. Перевод идёт по одному человеку, в момент входа.

Пользователь прислал пароль, вы проверили его старым способом и он подошёл — прямо здесь считаете новый хеш и переписываете строку. В записи хранится признак алгоритма, по нему и выбирается проверка. Через несколько месяцев на старом алгоритме остаются те, кто не заходил; их записи гасят принудительно, через сброс.

Схема при этом какое-то время держит оба формата, и это тот же приём, что и в миграциях без простоя: расширить — переехать — сузить.

Что делать, когда база всё-таки утекла

Стоимость перебора покупает не безопасность, а время. Правильно посчитанный Argon2id превращает разбор базы из ночной работы в месяцы на арендованном железе — и эти месяцы существуют для того, чтобы ими воспользоваться.

Порядок действий короткий. Признать утечку и назвать дату. Обнулить все активные сессии — хеши устоят, а вот украденный идентификатор сессии работает сразу и никакой стойкости не требует. Заставить сменить пароль при следующем входе, а не письмом с просьбой. И заново посчитать фактор стоимости: раз база ушла, значит, у нападающего теперь есть время, которого не было.

Что здесь ломается чаще всего — не хеши, а именно сессии. Про них вспоминают последними, хотя срабатывают они первыми.

Чего не делать

Не ограничивать длину и состав пароля сверху. Требование «не более 16 символов, обязательно цифра и знак» выдаёт, что пароль где-то хранится как есть или лежит в поле фиксированной ширины. Хеш всегда одной длины, какой бы ни была строка на входе.

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

Не писать своё. Здесь нечего улучшать, а цена ошибки видна только после утечки.

Условие, при котором всё это неважно

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

Оговорка ровно одна: вход через поставщика переносит риск, а не убирает его. Ваша база перестаёт быть добычей, но доступ к почте пользователя теперь открывает и ваш продукт тоже.