Пароли и KDF: перебор, радужные таблицы, GPU
Пароль — единственный секрет, который выбирает человек, а не генератор. Поэтому его стойкость измеряется не длиной ключа, а тем, сколько попыток нужно, чтобы его угадать, и сколько стоит каждая попытка. Криптография тут вторична: она лишь задаёт цену попытки, а всё остальное решает словарь.
Тема разбирает хранение паролей от начала до конца. Сначала — почему хранить хеш недостаточно: одинаковые пароли дают одинаковые хеши, и одна утёкшая база превращается в словарь для следующей. Потом соль и перец, затем конструкции, замедляющие перебор: PBKDF2, scrypt, Argon2. Дальше — экономика атаки: сколько стоит перебор на GPU, что даёт радужная таблица и почему 2^32 вариантов — это не «много», а несколько минут. Завершается выбором параметров, при которых защита переживает ближайшие годы.
Почему пароли ломаются
Энтропия человеческого выбора
Люди выбирают пароли из маленького распределения: слова, имена, даты, шаблоны вроде Имя2024!. Оценки показывают, что реальная энтропия типичного пароля — десятки бит, а не сотни 8. Перебор по словарю с мутациями покрывает значительную долю реальных паролей, и именно поэтому важно не «сколько бит в пароле», а «сколько попыток в секунду может позволить себе противник».
Офлайн и онлайн
Онлайн-атака ограничена скоростью сервера: несколько попыток в секунду, блокировки, капчи. Офлайн-атака начинается после утечки хешей: противник перебирает локально, без ограничений. Вся защита хранения паролей направлена на вторую ситуацию: даже получив базу, атакующий должен потратить годы.
Хранение: хеш, соль, перец
Просто хеш
Если база хранит H(пароль), то одинаковые пароли выглядят одинаково, а готовая таблица «хеш → пароль» ломает всю базу мгновенно. Хеш без соли — это не protector, а отпечаток.
Соль
Соль — случайное значение, уникальное для каждой записи и хранимое рядом с хешем: H(salt || пароль). Она уничтожает предвычисления: таблица, построенная для одной соли, бесполезна для другой. Соль не обязана быть секретной — её задача уникальность, а не секретность.
Перец
Перец — секретное значение, общее для приложения и хранимое вне базы (в конфигурации, в HSM). Он защищает от сценария «утёкли только хеши»: без перца перебор невозможен даже при знании соли. Цена — ротация перца требует пересчёта всех хешей, и перец не спасает, если утекла и конфигурация.
Медленные KDF
Соль и перец не мешают перебирать конкретный пароль — они лишь запрещают предвычисления. Замедляет перебор функция выработки ключа: PBKDF2 с большим числом итераций, scrypt с большими N, r, p, Argon2id с памятью. Идея одна: сделать каждую попытку дорогой по времени и памяти, чтобы преимущество GPU и ASIC уменьшилось 123.
PBKDF2: DK = F(P, S, c, dkLen) c итераций HMAC
scrypt: DK = scrypt(P, S, N, r, p) память ~128·N·r байт
Argon2id: память m, проходы t, параллелизм p
PBKDF2 дёшев по памяти, поэтому хорошо параллелится на GPU. scrypt требует памяти, что ограничивает параллелизм. Argon2id — современный выбор, учитывающий и память, и время, и устойчивость к сторонним каналам.
Парольный хеш — не то же самое, что KDF из ключа
Слово «KDF» покрывает две разные задачи, и путать их опасно. Первая — выработка ключа из уже случайного секрета: вход имеет полную энтропию, и задача функции — растянуть его, разделить по назначению и связать с контекстом. Для этого служит HKDF: Extract собирает энтропию, Expand выдаёт нужное число байт с метками. Вторая — хранение пароля: вход имеет низкую энтропию, и задача функции — сделать каждую попытку дорогой. Для этого служат PBKDF2, scrypt, Argon2id, и HKDF здесь не годится: он быстр по построению, а быстрота — именно то, чего нельзя давать перебирающему.
Практическое следствие: HKDF не заменяет парольный хеш, а парольный хеш не заменяет HKDF. Если из пароля нужно получить ключ шифрования, сначала выполняется медленный парольный хеш, а уже его выход подаётся в HKDF как секрет.
Перец без самодеятельности
Перец — секрет, общий для приложения, и стандартный способ его применения — не «дописать строку к паролю и надеяться», а передать его в KDF отдельным параметром: как ключ HMAC в PBKDF2, как дополнительный вход Argon2 или как ключ обёртки вокруг уже посчитанного хеша, хранимого в HSM. Конструкция H(pepper ‖ salt ‖ password) встречается в самодельных схемах и опасна тем, что её стойкость зависит от порядка и от того, что перец не попадёт в тот же дамп, что и хеши; кроме того, при ротации такой схемы приходится пересчитывать всё и следить, чтобы старые записи не сверялись с новым перцем. Перец хранится отдельно от базы (конфигурация, KMS, HSM), ротируется с пересчётом или с версионированием, и не считается секретом, если утечка базы и утечка конфигурации происходят вместе.
Онлайн-защита
Медленный KDF защищает только от офлайн-перебора по украденной базе. Пока база не украдена, пароль подбирают через сам сервис, и там работают другие меры: ограничение частоты попыток на аккаунт и на IP, экспоненциальная задержка, блокировка после серии неудач, обязательная многофакторная аутентификация для чувствительных операций, обнаружение credential stuffing по распределённым источникам и по спискам утёкших паролей, уведомление пользователя о входе с нового устройства. Проверка пароля по списку скомпрометированных (например, по диапазонам хешей) должна происходить при установке, а не после инцидента. Офлайн-стойкость и онлайн-стойкость — разные бюджеты, и оба нужны.
Экономика перебора
Скорость
Порядок величин: MD5 и SHA-1 на хорошей GPU — миллиарды в секунду; SHA-256 — сотни миллионов; PBKDF2 с 100 000 итераций — тысячи; scrypt с 64 МБ — сотни; Argon2id с 64 МБ — десятки. Разница в шесть-семь порядков — и есть вся защита.
Оценка
Для случайного пароля из алфавита A длины L число вариантов |A|^L. Для словаря с мутациями — число слов, умноженное на число шаблонов. Время — варианты, делённые на скорость. Отсюда практическое правило: подбирайте параметры так, чтобы одна попытка стоила порядка 0,1–0,5 секунды на целевом железе; тогда перебор миллиона словарных вариантов займёт сутки на каждую утёкшую запись.
Память важнее скорости
GPU силён в параллельных вычислениях с малым объёмом памяти на поток. Функция, требующая 64 МБ на попытку, вынуждает GPU обрабатывать десятки потоков вместо тысяч — выигрыш атакующего падает на порядки. Именно поэтому scrypt и Argon2 считаются лучше PBKDF2 при равном времени.
Радужные таблицы
Идея
Радужная таблица — компромисс между памятью и временем. Строятся цепочки: стартовое значение, затем h, затем R (reduction — сжатие хеша обратно в пространство паролей), и так t раз. В таблице хранятся только начало и конец цепочки. При поиске пароля по хешу цепочка «восстанавливается» от известного хеша и сверяется с концами 67.
Почему соль их убивает
Таблица строится для конкретной функции без соли. Уникальная соль делает таблицу бесполезной: для каждой записи нужна своя таблица, и выигрыш исчезает. Поэтому радужные таблицы — аргумент в пользу соли, а не против хеширования.
Что показывает учебная задача
В работе вы строите мини-таблицу для маленького пространства и видите выигрыш по памяти. Главный вывод: радужная таблица ускоряет перебор только при отсутствии соли и медленной KDF; вместе они закрывают этот путь полностью.
Утечки и повторное использование
Credential stuffing
Люди повторяют пароли. Утёкшая база одного сервиса становится словарём для другого, и это работает лучше любого перебора. Защита — не в KDF, а в организационных мерах: проверка паролей по утёкшим базам, второй фактор, ограничение попыток.
Что делать после утечки
Сменить пароли, инвалидировать сессии, проверить, не использовался ли тот же пароль в других местах, уведомить пользователей. Хеши в базе должны быть пересчитаны с новыми параметрами, если старые оказались слабыми.
Выбор параметров
Практические ориентиры
OWASP рекомендует PBKDF2-HMAC-SHA256 не менее 600 000 итераций, scrypt — N=2^17, r=8, p=1, Argon2id — 19 МиБ памяти, 2 прохода, 1 поток как минимум 5. NIST SP 800-63B требует, чтобы KDF была «дорогой» и параметры пересматривались 4. Конкретные числа меняются вслед за железом: важно не значение, а процедура — измерять на своём железе и обновлять.
Смена параметров
Параметры хранятся вместе с хешем (алгоритм, соль, итерации, память). При входе хеш проверяется старыми параметрами; если они устарели, пароль пересчитывается с новыми. Это позволяет обновлять защиту без сброса паролей.
Итоги темы
- Стойкость пароля определяется распределением, из которого его выбирают, и ценой одной попытки.
- Хеш без соли позволяет предвычисления и одинаковые отпечатки; соль обязательна.
- Перец защищает от утечки только хешей, но не заменяет KDF.
- Медленная KDF и память — основная защита от офлайн-перебора.
- Радужные таблицы бесполезны при уникальной соли.
- Параметры измеряются на своём железе и пересматриваются; смена параметров не требует сброса паролей.
Литература
- RFC 8018: PKCS #5: Password-Based Cryptography Specification Version 2.1. 2017. — PBKDF2 и требования к параметрам.
- RFC 7914: The scrypt Password-Based Key Derivation Function. 2016. — scrypt и его параметры.
- RFC 9106: Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications. 2021. — Argon2id и рекомендации.
- NIST. SP 800-63B: Digital Identity Guidelines — Authentication and Lifecycle Management. 2017. — требования к хранению аутентификаторов.
- OWASP. Password Storage Cheat Sheet. — практические рекомендации по алгоритмам и параметрам.
- Hellman M. A Cryptanalytic Time-Memory Trade-Off. IEEE Transactions on Information Theory, 1980. — идея компромисса память-время.
- Oechslin P. Making a Faster Cryptanalytic Time-Memory Trade-Off. CRYPTO 2003. — радужные таблицы.
- Bonneau J. The Science of Guessing: Analyzing an Anonymized Corpus of 70 Million Passwords. IEEE S&P 2012. — распределение реальных паролей.