Апокриф 08

Пароли и KDF: перебор, радужные таблицы, GPU

Пароли и 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 МБ — десятки. Разница в шесть-семь порядков — и есть вся защита.

Стоимость одной попытки
Рисунок 1. Цена одной попытки растёт на порядки от MD5 к Argon2id; это и есть защита от офлайн-перебора

Оценка

Для случайного пароля из алфавита A длины L число вариантов |A|^L. Для словаря с мутациями — число слов, умноженное на число шаблонов. Время — варианты, делённые на скорость. Отсюда практическое правило: подбирайте параметры так, чтобы одна попытка стоила порядка 0,1–0,5 секунды на целевом железе; тогда перебор миллиона словарных вариантов займёт сутки на каждую утёкшую запись.

Память важнее скорости

GPU силён в параллельных вычислениях с малым объёмом памяти на поток. Функция, требующая 64 МБ на попытку, вынуждает GPU обрабатывать десятки потоков вместо тысяч — выигрыш атакующего падает на порядки. Именно поэтому scrypt и Argon2 считаются лучше PBKDF2 при равном времени.

Радужные таблицы

Идея

Радужная таблица — компромисс между памятью и временем. Строятся цепочки: стартовое значение, затем h, затем R (reduction — сжатие хеша обратно в пространство паролей), и так t раз. В таблице хранятся только начало и конец цепочки. При поиске пароля по хешу цепочка «восстанавливается» от известного хеша и сверяется с концами 67.

Цепочка радужной таблицы
Рисунок 2. Цепочка h → R → h: в таблице хранятся только начало и конец, но соль делает таблицу бесполезной

Почему соль их убивает

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

Что показывает учебная задача

В работе вы строите мини-таблицу для маленького пространства и видите выигрыш по памяти. Главный вывод: радужная таблица ускоряет перебор только при отсутствии соли и медленной 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 и память — основная защита от офлайн-перебора.
  • Радужные таблицы бесполезны при уникальной соли.
  • Параметры измеряются на своём железе и пересматриваются; смена параметров не требует сброса паролей.

Литература

  1. RFC 8018: PKCS #5: Password-Based Cryptography Specification Version 2.1. 2017. — PBKDF2 и требования к параметрам.
  2. RFC 7914: The scrypt Password-Based Key Derivation Function. 2016. — scrypt и его параметры.
  3. RFC 9106: Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications. 2021. — Argon2id и рекомендации.
  4. NIST. SP 800-63B: Digital Identity Guidelines — Authentication and Lifecycle Management. 2017. — требования к хранению аутентификаторов.
  5. OWASP. Password Storage Cheat Sheet. — практические рекомендации по алгоритмам и параметрам.
  6. Hellman M. A Cryptanalytic Time-Memory Trade-Off. IEEE Transactions on Information Theory, 1980. — идея компромисса память-время.
  7. Oechslin P. Making a Faster Cryptanalytic Time-Memory Trade-Off. CRYPTO 2003. — радужные таблицы.
  8. Bonneau J. The Science of Guessing: Analyzing an Anonymized Corpus of 70 Million Passwords. IEEE S&P 2012. — распределение реальных паролей.