---
tags: [security]
verified: 2026-09-14
---

# KDF для паролей: почему медленно и с памятью, а не быстро

## Две разные задачи под одним словом

«Вывод ключа» означает две противоположные вещи, и от того, какая из них имеется в виду, зависит буквально всё — в том числе знак требования к скорости.

Первая задача разобрана в [статье про обмен ключами](./crypto-key-exchange.md): на входе уже есть секрет, у которого достаточно энтропии, но он неравномерен и не размечен по ролям. `HKDF-Extract` собирает энтропию в равномерную строку, `HKDF-Expand` разводит её на именованные ключи. Быстрота здесь — достоинство: рукопожатие делается на каждое соединение, а противнику перебирать нечего — гадать про 32 байта из CSPRNG бессмысленно.

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

Смешение этих двух ролей порождает обе типовые ошибки сразу. «Возьмём SHA-256 для паролей» — это применение инструмента первой задачи ко второй. «Прогоним HKDF тысячу раз» — попытка починить инструмент первой задачи, не поняв, что чинить надо не число проходов, а то, во что упирается атакующий. Ниже разбирается, во что именно он упирается, почему ответ трижды менялся вслед за железом и как из всего этого получаются конкретные параметры.

Статья закрывает вопрос, оставленный открытым таблицей префиксов в [заметке про пользователей и PAM](./linux-users-pam.md): там сказано, что yescrypt устойчив к перебору на GPU, но не сказано почему. Граница между текстами проходит не по именам настроек, а по вопросу: «как это настроить и где править» — там, «почему в разных системах вышли разные умолчания и что за ними стоит» — здесь. Поэтому `ENCRYPT_METHOD` встретится и ниже, но как наблюдаемое следствие выбора, а не как инструкция.

Одну вещь стоит назвать сразу, потому что её отсутствие бросается в глаза. В статье нет ни одной цифры вида «столько-то миллиардов попыток в секунду на такой-то карте», и это решение, а не пробел: такие замеры привязаны к модели карты, версии реализации и настройкам самого переборщика, устаревают за год-полтора и отвечают на вопрос о скорости, тогда как решается вопрос о площади — сколько параллельных экземпляров вообще помещается в память. Всё, что нужно для второго счёта, ниже есть; там, где появляется оценка «сколько попыток влезет на карту», это арифметика по памяти, а не бенчмарк, и так она и помечена.

---

## Пароль — не ключ

### Насколько именно не ключ

Порядки величин стоит держать в голове, потому что из них следует всё остальное. Бонно в [«The Science of Guessing»](http://www.umiacs.umd.edu/~tdumitra/courses/ENEE759D/Fall13/papers/Bonneau12.pdf) (IEEE S&P 2012) разобрал корпус почти из 70 миллионов паролей Yahoo! и оценил их стойкость так: меньше 10 бит против онлайнового перебора и около 20 бит против оптимальной офлайновой атаки по словарю. Двадцать бит — это миллион вариантов. Симметричный ключ, о котором идёт речь в соседних статьях цикла, — 128 бит.

Разрыв в сотню бит не закрывается никаким KDF, и это надо сказать прямо, потому что на этом держится вся дальнейшая арифметика. [RFC 8018](https://www.rfc-editor.org/rfc/rfc8018.html), §4.2, формулирует предельно честно: счётчик итераций `c` увеличивает стойкость пароля на `log2(c)` бит. Модные сегодня 600 000 итераций PBKDF2 — это плюс девятнадцать бит. Защитник платит за них линейно, атакующий расплачивается логарифмом.

Значит, задача KDF — не догнать 128 бит, а сдвинуть экономику: сделать так, чтобы перебор миллиона правдоподобных вариантов по всей утёкшей базе стоил дороже, чем пароли в ней стоят. Это ровно та формулировка, которую даёт RFC 9106, §7.3, описывая рационального защитника: он максимизирует стоимость перебора для атакующего при фиксированном времени вычисления на своей машине. Не «при фиксированной стойкости», а при фиксированном своём времени — потому что своё время и есть единственный дефицитный ресурс.

### Соль публична, и это не оговорка

Соль решает три задачи, и ни одна из них не требует секретности. Страница `crypt(5)` из libxcrypt перечисляет их в явном виде: нельзя посчитать хеш пароля один раз и проверить его против всех учётных записей сразу; нельзя узнать, что у двух записей одинаковый пароль, не угадав хотя бы один из них; таблица предвычисленных хешей должна содержать по записи на каждое значение соли, что делает её непрактично большой.

Третий пункт — это и есть ответ радужным таблицам, но обратите внимание, что он в списке не первый. Важнее амортизация: без соли стоимость атаки на базу из миллиона записей равна стоимости атаки на одну. Соль ломает именно это, и потому она обязана быть уникальной на запись, а не просто случайной. Длину задают трое, и разброс между ними четырёхкратный: [SP 800-63B](https://pages.nist.gov/800-63-4/sp800-63b.html) (редакция 4, 26 августа 2025) требует не меньше 32 бит, [RFC 8018](https://www.rfc-editor.org/rfc/rfc8018.html), §4.1, советует не меньше восьми октетов, то есть 64 бит, [RFC 9106](https://www.rfc-editor.org/rfc/rfc9106.html) рекомендует 128. Читать это стоит так: 32 бита — нижняя граница, ниже которой начинается нарушение «SHALL», 128 — норма, и экономить между ними не на чем, потому что соль не стоит ничего ни в вычислениях, ни в хранении.

### Перец отвечает на другую угрозу

Перец (pepper) — секретное значение, общее на всю установку и хранящееся отдельно от хешей. Оно защищает от строго одного сценария: утёк дамп базы, но не утёк конфиг. SP 800-63B-4 рекомендует это отдельным SHOULD: выполнить дополнительный проход keyed-хешированием или шифрованием на ключе, известном только верификатору, причём «the secret key value SHALL be stored separately from the hashed passwords» и хранить его следует в защищённом аппаратно контуре — HSM или TEE.

Дальше начинается развилка, и рекомендации по ней расходятся. Внутренний вариант подмешивает перец к паролю до хеширования, внешний накладывает его на уже готовый хеш. OWASP описывает оба и для bcrypt прямо советует внутренний: `bcrypt(base64(hmac-sha384(data:$password, key:$pepper)), $salt, $cost)`. Доводы там конкретные и оба вне темы перца: предварительный HMAC укладывает пароль любой длины в 72 байта bcrypt и закрывает password shucking — приём, при котором атакующий сверяет не пароль, а утёкший откуда-то ещё хеш `H($password)`, сводя взлом bcrypt к взлому быстрой функции `H`. Цена названа в том же документе, в общих требованиях к перцу: сменить перец, не зная паролей, нельзя, поэтому его компрометация означает принудительный сброс паролей у всех.

Вердикт здесь авторский, и он в пользу внешнего варианта — ровно из-за сменяемости. Ключ, который годами лежит в HSM, рано или поздно придётся ротировать, а массовый сброс паролей по ущербу сопоставим с той самой утечкой, от которой перец и защищает. Одна оговорка существенна и обычно проглатывается: дёшево ротируется только шифрование поверх хеша — расшифровать старым ключом, зашифровать новым; внешний HMAC необратим точно так же, как внутренний, и сменяемости не даёт. SP 800-63B-4 разрешает оба примитива («keyed hashing or encryption operation») — из этих двух надо брать шифрование. Довод OWASP про 72 байта при этом не опровергается, он просто про другое: он доказывает, что длинный пароль перед bcrypt надо пред-хешировать, а не то, что делать это следует на секретном ключе.

---

## Три поколения, и каждое — ответ на подешевевшее железо

Перечислять поколения в хронологическом порядке было бы бессмысленно: интересна не последовательность, а причинность. Каждое следующее появилось потому, что предыдущее проигрывало не криптоанализу, а прогрессу в кремнии.

Формулировку этого проигрыша дал Персиваль во введении к [«Stronger Key Derivation via Sequential Memory-Hard Functions»](https://www.tarsnap.com/scrypt/scrypt.pdf) (BSDCan'09), и она стоит того, чтобы привести её целиком по смыслу. Правильная единица измерения стоимости вычисления — доллар-секунды, а не операции. По мере развития полупроводниковой технологии схемы не просто становятся быстрее — они становятся меньше, а значит, за те же деньги помещается больше параллельных экземпляров. Следствие: «using existing key derivation algorithms, even if the iteration count is increased such that the time taken to verify a password remains constant, the cost of finding a password by using a brute force attack implemented in hardware drops each year». То есть схема, настроенная на постоянное время проверки, дешевеет для атакующего сама по себе, без единой правки конфигурации.

### Итерации: дорого по времени, дёшево по площади

PBKDF2 ([RFC 8018](https://www.rfc-editor.org/rfc/rfc8018.html), он же PKCS #5 v2.1) применяет HMAC заданное число раз и складывает результаты по XOR. Единственный параметр — счётчик итераций. Состояние функции — два состояния хеша и пара блоков буфера, то есть сотни байт.

Именно этот размер и проигрывает. Задача перебора embarrassingly parallel: каждая догадка независима, синхронизировать нечего. Кристалл, на котором помещается сотня байт состояния, вмещает столько ядер перебора, сколько влезет по площади ALU, — и с каждым технологическим узлом их становится больше. Защитник при этом привязан к одному ядру своего процессора: ему надо уложиться в интерактивную задержку.

Цифры из RFC 8018 сегодня выглядят анекдотично, и полезно понимать, что это не ошибка авторов, а возраст документа: «a modest number of iterations (say, 1000) is not likely to be a burden for legitimate parties». Документ повторяет рекомендацию [NIST SP 800-132](https://csrc.nist.gov/pubs/sp/800/132/final) — минимум 1000, для особо критичных ключей может подойти 10 000 000. [Рекомендация OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) на дату сверки — 600 000 итераций с HMAC-SHA-256, и она приведена там ровно с одной оговоркой: «if FIPS-140 compliance is required».

Вердикт: PBKDF2 в новой системе — только под внешнее требование. Это не мнение: SP 800-132 остаётся действующим документом 2010 года, NIST объявил о намерении его пересмотреть в мае 2023-го — с прямо названной целью одобрить дополнительную память-жёсткую функцию, — и на дату сверки черновик не опубликован. Пока он не выйдет, «одобренный NIST парольный KDF» означает PBKDF2 и ничего больше, и цепочка замыкается: SP 800-63B-4 в разделе про хранение паролей отсылает к «an approved password hashing scheme published in the latest revision of [SP800-132] or updated NIST guidelines».

### Память: перенос стоимости туда, где нет преимущества

scrypt ([RFC 7914](https://www.rfc-editor.org/rfc/rfc7914.html), 2016; работа Персиваля — 2009) добавляет второй ресурс. Функция `ROMix` заполняет массив из `N` блоков по `128·r` байт последовательно, а затем `N` раз читает из него по адресу, зависящему от только что вычисленного значения. Второй проход и делает всю работу: адрес неизвестен заранее, поэтому весь массив обязан лежать в памяти целиком.

Честная мера здесь — не время и не память по отдельности, а их произведение. RFC 9106, §7.2, называет её прямо: `time-area product`, где память и дополнительные ядра дают площадь, а время растёт из-за перевычислений. Смысл перехода к памяти в том, что площадь памяти покупается за деньги в куда более линейной пропорции, чем площадь логики, и у атакующего на ней нет преимущества перед защитником: гигабайт DRAM стоит одинаково обоим.

Слово «обязан лежать» требует уточнения, и в нём — разница между поколениями. Память можно разменять на время: хранить половину блоков, а недостающие пересчитывать. Такой размен называется TMTO, и scrypt к нему намеренно дружелюбен — в [сравнении от автора yescrypt](https://github.com/openwall/yescrypt/blob/main/COMPARISON) это сформулировано как «scrypt is deliberately TMTO-friendly (and moreover, computing it in less memory takes up to 4x less than proportionately longer)». Argon2 и yescrypt, наоборот, TMTO-стойкие: считать их в меньшей памяти непропорционально дольше.

Порядок выигрыша виден по таблице 1 в работе Персиваля — стоимости оборудования, способного найти пароль в среднем за год. Колонку надо называть точно, иначе числа не сойдутся: речь про «8 chars» — восемь знаков, выбранных из 95 печатных ASCII. В ней стоит $18 тыс. у PBKDF2 (100 мс), $130 тыс. у bcrypt (95 мс) и $4,8 млн у scrypt (64 мс); в соседней колонке «8 letters», где восемь случайных строчных букв, у scrypt всего $150 — в тридцать тысяч раз меньше, и перепутать их, открыв PDF, легко. Для интерактивного логина автор подытоживает всю таблицу как 35-кратный выигрыш над bcrypt и 260-кратный над PBKDF2. Оговорка самих авторов существенна и её нельзя опускать: значения «very approximate» и отражают стоимость только криптографической части схемы по технологии примерно 2002 года. Полезно здесь отношение между строками, а не абсолютные суммы.

### Настраиваемые профили: три параметра вместо одного

Argon2 выиграл конкурс Password Hashing Competition в июле 2015 года (конкурс шёл с 2013-го, [четыре финалиста получили особое упоминание](https://www.password-hashing.net/)) и описан в [RFC 9106](https://www.rfc-editor.org/rfc/rfc9106.html). Параметров три: `m` — память, `t` — число проходов, `p` — число дорожек (lanes), то есть степень параллелизма внутри одного вычисления.

Третий параметр — не бесплатное ускорение, и это тонкость, которую обычно пропускают. Дорожки позволяют защитнику занять несколько ядер и уложиться в бюджет времени при большей памяти. Но тот же параллелизм достаётся атакующему: память одного вычисления оказывается разделена между потоками, и на GPU это превращается в возможность потратить больше вычислительных ресурсов на ту же суммарную память. Именно за это Argon2 критикуют авторы yescrypt, и критика конкретна: Argon2 допускает параллельную обработку восьми 128-байтных кусков внутри килобайтного блока всего с двумя точками синхронизации на блок.

Вариантов три, и различаются они тем, как выбирается адрес для чтения:

| Вариант | Адрес зависит от | Устойчив к | Уязвим к |
| --- | --- | --- | --- |
| Argon2d | обрабатываемых данных | TMTO лучше всех: лучший размен даёт всего 1,33× | утечке по времени доступа к памяти |
| Argon2i | только от индексов | атакам по сторонним каналам | TMTO: для 1–2 проходов известен размен в 5× |
| Argon2id | первая половина первого прохода как 2i, дальше как 2d | и к тому, и к другому | лучший размен для одного прохода — около 2,1× |

Числа в таблице — из RFC 9106, §7.2. Из них же следует и вердикт самого RFC, и он безальтернативен: «if you do not know the difference between the types or you consider side-channel attacks to be a viable threat, choose Argon2id». Argon2d осмыслен там, где атакующий заведомо не делит машину с защитником, — RFC называет майнинг криптовалют; Argon2i отдельно брать не за чем, потому что его стойкость к сторонним каналам целиком содержится в 2id.

### Вердикт по поколениям

| Функция | Что настраивается | Память при типовых настройках | Куда упирается атакующий | Когда брать |
| --- | --- | --- | --- | --- |
| PBKDF2 | итерации | сотни байт | площадь логики; растёт с каждым техпроцессом | только под требование FIPS |
| bcrypt | цена ключевого расписания | 4 KiB, фиксировано | локальная память ядра GPU — но 4 KiB в неё помещаются | не переписывать существующее |
| scrypt | `N`, `r`, `p` | от единиц до сотен MiB | площадь памяти, но с поблажкой на TMTO | когда нет Argon2id, и в готовых форматах |
| yescrypt | один логарифмический cost в интерфейсе `crypt()` | 16 MiB при умолчании libxcrypt | площадь памяти плюс задержка умножений и кэш | системный `crypt()` |
| Argon2id | `m`, `t`, `p` независимо | от 19 MiB до гигабайтов | площадь памяти, TMTO не помогает | новый код, где есть библиотека |

Для нового кода — Argon2id: у него единственного параметры разведены так, что бюджет по памяти и бюджет по времени задаются независимо, и он есть во всех живых экосистемах. Существующий bcrypt переписывать не надо: он не сломан, а цена миграции парольных хешей — принудительная смена паролей или ожидание естественной ротации. PBKDF2 и sha512crypt — ровно наоборот: не сломаны криптографически, но проигрывают по устройству, и их стоит менять при первой возможности сменить бесплатно.

---

## Что стоит в системах на самом деле

### Интерфейс решает за алгоритм

Хеш в `/etc/shadow` вычисляет `crypt(3)`, а интерфейс у неё — из семидесятых: одна строка на вход, одна на выход, все параметры закодированы внутри строки-настройки. Реализацию давно поставляет не libc: glibc собирала libcrypt по умолчанию последний раз в 2.37 — [в 2.38](https://sourceware.org/git/?p=glibc.git;a=blob;f=NEWS;hb=refs/tags/glibc-2.38) сборка отключена по умолчанию и `--enable-crypt` стал обязательным, — а в 2.39 (январь 2024) её убрали совсем вместе с опциями `--enable-crypt` и `--enable-nss-crypt`. На всех массовых дистрибутивах на её месте стоит [libxcrypt](https://github.com/besser82/libxcrypt).

Отсюда следует то, что объясняет умолчания лучше любых сравнений алгоритмов: в системном `shadow` может оказаться только то, что реализовано в libxcrypt. `crypt(5)` перечисляет полный список — yescrypt, gost-yescrypt, sm3-yescrypt, scrypt, bcrypt, sha512crypt, sha256crypt и далее вниз по возрасту. Argon2 в нём нет. Новость Arch Linux о смене умолчания называет это первым доводом: yescrypt выбран «due to its adoption (readily available in libxcrypt, which is used by pam)», и там же прямо сказано, что Argon2 выиграл PHC, но в libxcrypt его нет.

### yescrypt: почему он, а не Argon2id

yescrypt спроектирован Solar Designer как надстройка над scrypt и получил на PHC особое упоминание, не выиграв конкурс. Технический довод в пользу него сформулирован в [README проекта](https://github.com/openwall/yescrypt) и стоит того, чтобы прочитать его внимательно: у память-жёстких схем «at high target throughput and/or low target latency their memory usage is unreasonably low, up to the point where they're not obviously better than the much older bcrypt (considering attackers with pre-existing hardware)».

Это и есть ключ ко всему разделу. Логин — это как раз режим низкой задержки: на проверку пароля можно потратить десятки миллисекунд, и в них влезает не два гигабайта, а полтора-два десятка мегабайт. На таком бюджете память сама по себе почти ничего не покупает — GPU с несколькими гигабайтами памяти всё ещё держит сотни параллельных попыток. yescrypt добавляет второй механизм: латентность вычисления, завязанную на целочисленное умножение и скорость локальной памяти. По собственной оценке авторов, yescrypt выполняет 96 последовательных умножений на килобайт против 16 у Argon2, и это даёт гарантированный минимум времени независимо от того, насколько быстрая память у атакующего.

Три довода, которыми [предложение Fedora 35](https://fedoraproject.org/wiki/Changes/yescrypt_as_default_hashing_method_for_shadow) обосновывает выбор yescrypt вместо Argon2, повторяют этот же сюжет: зависимость не только от RAM, но и от быстрой памяти на кристалле, что даёт «bcrypt-like anti-GPU properties even at very low per-hash RAM sizes»; меньше низкоуровневого параллелизма, который можно извлечь на GPU; и криптографическая часть на SHA-256, HMAC и PBKDF2, то есть на одобренных NIST примитивах, против BLAKE2 у Argon2.

Умолчание конкретно: libxcrypt при cost-факторе 5 (значение по умолчанию, диапазон 1–11) ставит `N = 4096`, `r = 32`, `p = 1`, что даёт `128 · 32 · 4096` = 16 MiB на хеш. Это в точности «large and slow» из [рекомендаций автора](https://github.com/openwall/yescrypt/blob/main/PARAMETERS), где он оценивается как сопоставимый с bcrypt cost 2⁸: латентность 10–30 мс и больше тысячи хешей в секунду на шестнадцатиядерном сервере.

Обратная сторона названа самими авторами и в статье замалчиваться не должна: yescrypt сложен («higher risk of human error occurring and remaining unnoticed for long»), небезопасен по кэш-таймингу, как scrypt и Argon2d, не является победителем PHC и поддерживается в заметно меньшем числе сторонних проектов. Для выделенного сервера аутентификации первые два пункта почти не весят, для библиотеки общего назначения — весят сильно.

### bcrypt: почему держится и где кончается

bcrypt (Провос и Мазьер, USENIX 1999) устроен вокруг блочного шифра Blowfish с намеренно дорогим ключевым расписанием. Его живучесть объясняется одной фразой из [оригинальной работы](https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node4.html): «the eksblowfish S-Boxes require 4 KB of constantly accessed and modified memory. Thus, the S-Boxes cannot be shared across key schedules — separate S-Boxes must exist for every simultaneous execution. This vastly limits the usefulness of any attempts to pipeline the Feistel network in hardware». Иными словами, bcrypt был память-жёстким до того, как термин появился, — просто с фиксированными и очень маленькими 4 килобайтами.

Отсюда обе его черты. Живучесть: 4 KiB на попытку четверть века мешали строить дешёвые конвейеры. Потолок: 4 KiB прекрасно помещаются в локальную память вычислительного блока современного GPU, а поднять их нельзя — параметра памяти у bcrypt нет вовсе, цена регулируется только числом проходов ключевого расписания.

Второе ограничение — длина, и у него два разных числа. Оригинальная работа говорит о ключе «up to 56 bytes (including a terminating zero byte)», то есть о 55 значащих символах; Персиваль в 2009-м считал именно по этому пределу и объяснял им, почему bcrypt на длинных фразах проигрывает даже PBKDF2: «bcrypt's inability to use more than the first 55 characters of a passphrase». Современные реализации берут до 72 байт, и именно 72 документирует `crypt(5)`. Откуда взялся переход с 56 на 72 и в какой реализации он зафиксирован, подтвердить первоисточником не удалось — 56/55 и 72 — это одно расхождение, а не два, и проходит оно между работой 1999 года и тем, что делают сегодняшние библиотеки. Практически важнее другое: лишнее отбрасывается молча, длинная парольная фраза не даёт никакого выигрыша после порога, и пользователь об этом не узнает.

### sha512crypt: upstream-умолчание, которое не аргумент

В самом shadow-utils `ENCRYPT_METHOD` по умолчанию — `SHA512`, и это стоит понимать как возраст, а не как рекомендацию. sha512crypt — конструкция Дреппера 2008 года, циклическое применение SHA-512 заданное число раз; умолчание по числу раундов — 5000, и `crypt(5)` комментирует его без дипломатии: «too low for modern hardware».

Дефекты у него не криптографические, и предложение Fedora перечисляет их списком: эффективно используется около 90 бит соли при рекомендованных NIST ≥128; длинный пароль создаёт отказ в обслуживании по процессору; пассивное наблюдение за временем выполнения позволяет предсказать длину пароля; криптографическая функция вывода ключа не используется вовсе. Ни один из пунктов не делает существующие хеши подделываемыми — и все вместе они означают, что выбирать sha512crypt сегодня не за что.

### Кто и где выбирает

Выбор делает не `login.defs` напрямую: пароли пользователей записывает PAM. `pam_unix(8)` берёт умолчание из `ENCRYPT_METHOD` в `login.defs`, а если переменной нет и опция модулю не передана — использует встроенный SHA512. Как это правят, какие значения принимаются и почему на одной машине соседствуют `$6$` и `$y$` — в [заметке про пользователей и PAM](./linux-users-pam.md); здесь дальше — что в итоге оказалось умолчанием в разных системах и из-за чего они разошлись.

Что стоит в дистрибутивах на дату сверки — проверялось по исходникам пакетов, а не по обзорам:

| Система | `ENCRYPT_METHOD` | Чем проверено |
| --- | --- | --- |
| upstream shadow-utils | `SHA512` | `login.defs(5)` в дереве проекта |
| Debian (shadow 4.19.3-2) | `YESCRYPT` | `etc/login.defs` в исходниках пакета |
| Fedora 35 и новее | `YESCRYPT` | предложение изменения F35 |
| CentOS Stream 10 → RHEL 10 | `YESCRYPT` | `shadow-utils.login.defs`, ветка `c10s` |
| CentOS Stream 9 → RHEL 9 | `SHA512` | тот же файл, ветка `c9s` |
| Arch (shadow ≥ 4.14.0, сентябрь 2023) | `YESCRYPT` | новость дистрибутива |

Строка про RHEL 9 против RHEL 10 полезнее остальных: yescrypt в девятке поддерживается, но умолчанием не является, и «поддерживается» с «используется» путают постоянно.

### Сравнение с FreeBSD

Во FreeBSD выбор задаётся `passwd_format` в `login.conf`, умолчание — `sha512`. Выбирать `login.conf(5)` предлагает из пяти значений: `des`, `md5`, `blf` (bcrypt), `sha256`, `sha512`. Шестое, `nth`, есть только в таблице `crypt_formats[]` в [`lib/libcrypt/crypt.c`](https://cgit.freebsd.org/src/tree/lib/libcrypt/crypt.c) и к хранению паролей отношения не имеет — это NT-хеш для совместимости. Ни yescrypt, ни scrypt, ни Argon2 в системной `crypt(3)` нет. Практический вывод для администратора FreeBSD ровно один: осмысленных вариантов два, и `blf` из них лучше умолчания по всем разобранным выше причинам, кроме совместимости.

---

## Как выбирают параметры

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

**Шаг первый — назвать бюджет на одну проверку.** У сервера аутентификации он выводится из пиковой частоты входов: если надо держать сто проверок в секунду на четырёх ядрах, то на проверку есть 40 мс процессорного времени, и никакие рассуждения о стойкости этого не изменят. У разблокировки диска или открытия архива бюджет задаёт терпение человека — это одна-две секунды, то есть в полсотни раз больше. У парольного менеджера, открывающего базу однажды за сессию, — столько же.

**Шаг второй — померить на своём железе.** Чужие числа здесь бесполезны в точности так же, как в [сравнении симметричных шифров](./crypto-aes-vs-chacha20.md): меряется неизвестная машина неизвестной сборкой. Мерить нужно то, что реально будет считать хеш:

```bash
# scrypt: N, r, p напрямую; работает в любом OpenSSL 3.x
time openssl kdf -keylen 32 -kdfopt pass:test -kdfopt salt:0123456789abcdef \
  -kdfopt n:65536 -kdfopt r:8 -kdfopt p:1 \
  -kdfopt maxmem_bytes:1073741824 SCRYPT

# Argon2id: нужен OpenSSL 3.2 или новее, memcost в килобайтах
time openssl kdf -keylen 32 -kdfopt pass:test -kdfopt salt:0123456789abcdef \
  -kdfopt iter:2 -kdfopt memcost:19456 -kdfopt lanes:1 -kdfopt threads:1 ARGON2ID

# то, что реально будет стоять в заголовке LUKS2, — с калибровкой под эту машину
cryptsetup benchmark --pbkdf argon2id
```

Результаты ниже получены 14 сентября 2026 года на виртуальной машине с Intel Xeon 2,8 ГГц (4 vCPU), через `hashlib` из Python 3.11.15 поверх OpenSSL 3.0.13. Это характеристика одной машины, а не алгоритмов; смысл таблицы в отношении между строками, а не в абсолютных числах. Argon2id здесь не мерился: в OpenSSL 3.0.13 его нет, он появился в 3.2 (ноябрь 2023).

| Функция и параметры | Время, мс | Память на одну попытку |
| --- | --- | --- |
| PBKDF2-HMAC-SHA256, 100 000 итераций | 68 | сотни байт |
| PBKDF2-HMAC-SHA256, 600 000 итераций | 403 | сотни байт |
| PBKDF2-HMAC-SHA256, 1 000 000 итераций | 677 | сотни байт |
| scrypt, `N`=2¹⁴, `r`=8, `p`=1 | 45 | 16 MiB |
| scrypt, `N`=2¹⁵, `r`=8, `p`=1 | 105 | 32 MiB |
| scrypt, `N`=2¹⁶, `r`=8, `p`=1 | 210 | 64 MiB |
| scrypt, `N`=2¹⁷, `r`=8, `p`=1 | 431 | 128 MiB |

Две нижние строки и вторая — это и есть весь сюжет статьи в одном сравнении. При одинаковой цене для защитника (около 0,4 секунды) одна функция требует от атакующего сотни байт на параллельный экземпляр, а другая — 128 мегабайт.

**Шаг третий — посчитать, что бюджет даёт атакующему.** Считать надо не время, а площадь, и делится она в одно действие: память карты, поделённая на память одной попытки, даёт потолок числа одновременных попыток. При 19 MiB на попытку это около 430 на карте с 8 ГиБ, около 1290 на 24 ГиБ и около 4300 на ускорителе с 80 ГиБ: по всему диапазону от игровой карты до дата-центрового ускорителя счёт идёт на сотни и тысячи, сколько бы вычислительных блоков на них ни стояло. Поднимите память на попытку до 1 GiB — и те же три класса дадут 8, 24 и 80. При PBKDF2 память не ограничивает вовсе, и число параллельных попыток упирается только в то, сколько ядер поместилось на кристалле. Это оценка сверху по одному ресурсу, а не бенчмарк: реальная скорость будет ниже из-за пропускной способности памяти, — но для выбора между «память ограничивает» и «память не ограничивает» её достаточно.

Здесь же становится видно, зачем нужны рекомендованные наборы и почему они так расходятся. RFC 9106 первым вариантом называет Argon2id с `t`=1, `p`=4 и `m`=2²¹, то есть 2 GiB, и предлагает считать это умолчанием для всех сред. OWASP рекомендует минимум 19 MiB при `t`=2, `p`=1. Расхождение стократное, и оно не ошибка: RFC отвечает на вопрос «сколько стоит сделать одну проверку максимально дорогой», OWASP — на вопрос «что выдержит сервер, который аутентифицирует людей пачками». Приложенное к трём бюджетам из первого шага это сразу даёт вердикт. Сервер аутентификации с его 40 мс — случай OWASP, и 19 MiB для него не «минимум, от которого расти», а примерно всё, что в бюджет влезает. Разблокировка диска и парольный менеджер с их секундой-двумя — случай RFC: гигабайт-другой там реален, и именно на них набор 2 GiB рассчитан. Промежуточных постановок мало, и если ваша оказалась ровно посередине, то мерить по второму шагу придётся всерьёз.

**Шаг четвёртый — заложить, что параметры устареют.** Они стареют молча и без вашего участия: и RFC 8018, и SP 800-63B-4 требуют повышать стоимость со временем («it SHOULD be increased over time to account for increases in computing performance»). Для парольных хешей это дёшево — новый параметр применяется при следующей смене пароля, а старые записи продолжают проверяться по своим, потому что параметры лежат в самой строке хеша. Для шифрования по паролю это совсем не дёшево, и о том, почему, — следующий раздел.

### Отдельный случай: параметр пришёл от противника

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

Спецификация age разбирает это явно: работать полагается «SHOULD apply an upper limit to the work factor», и эталонная реализация действительно ограничивает принимаемый фактор значением 2²², помеченным в коде как «15s on a modern machine», тогда как при шифровании ставит 2¹⁸ — «1s on a modern machine». Если вы проектируете формат с параметрами в заголовке, потолок при чтении — обязательная часть конструкции, а не украшение.

---

## Соседняя задача: ключ для шифрования из пароля

Постановка меняется в двух местах. Первое очевидно: ключ не проверяется, а используется, и неверный пароль даёт не отказ, а мусор — если формат не позаботился о проверке отдельно. Механизм этой проверки и то, почему она не берётся из AEAD бесплатно, разобраны [в статье про AEAD](./crypto-aead.md) в разделе про обязательство по ключу; спецификация age там же, где запрещает смешивать парольную ступень с другими получателями, ссылается на partitioning oracle.

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

![Две конструкции парольного шифрования: слева выведенный из пароля ключ шифрует сами данные, и параметры KDF записаны в самом артефакте, поэтому смена параметров означает перешифрование всех данных; справа выведенный ключ шифрует только случайный мастер-ключ, лежащий в отдельном слоте хранилища, и смена параметров переписывает один слот, не трогая данные](./assets/crypto-password-kdf-key-wrapping.svg)

В прямой конструкции ключ, выведенный из пароля, и есть ключ данных. Параметры записаны там же, где данные, и меняются вместе с ними: чтобы перейти с фактора 2¹⁸ на 2²⁰, надо перешифровать артефакт целиком. Для однократно записываемого файла это нормально и честно; для растущего хранилища — неприемлемо.

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

Так устроены все три хранилища, которые встречаются в базе:

- **LUKS2.** Ключ слота выводится Argon2id — умолчание задано при сборке cryptsetup и в апстриме равно `argon2id`, 2000 мс на вывод, до 1 GiB памяти и до 4 потоков. Параметры не константы: cryptsetup бенчмаркует машину при создании слота, пытаясь уложить запрошенную память в заданное время и снижая память, если не выходит; при бенчмарке она всегда остаётся в диапазоне от 64 MiB до 1 GiB, выше 1 GiB её можно поставить только явным `--pbkdf-memory`. Смена параметров — это `luksAddKey` с новыми значениями и `luksKillSlot` для старого, что попадает в [заметку про восстановление системы](./system-recovery.md) с другой стороны: заголовок с этими слотами и есть то, что бэкапят `luksHeaderBackup`.
- **restic.** KDF — scrypt, и параметры не зашиты в код: при создании ключа `Calibrate` подбирает их под машину с потолком в 500 мс и 60 MiB (`KDFMemory = 60` уходит в `sscrypt.Calibrate(timeout, memMiBytes, params)`, то есть считается в мебибайтах), после чего `N`, `r` и `p` записываются в файл ключа рядом с обёрнутым мастер-ключом. Калибровка повторяется на каждом добавлении ключа: параметры принадлежат ключу, а не репозиторию, так что переход на более тяжёлые — это новый ключ, подобранный на текущем железе, и удаление старого. Соседняя [заметка про бэкапы](./backup-strategy.md) разбирает, где живёт пароль и чем `repokey` отличается от `keyfile`; здесь же существенно, что обе схемы borg — тоже обёртка.
- **GELI во FreeBSD.** Та же архитектура с мастер-ключом и слотами, но парольная ступень — PBKDF2 (`PKCS#5v2`), и число итераций по умолчанию подбирается под две секунды работы. Бюджет тот же, что у LUKS2, а поколение — первое: памяти в этой конструкции нет. Это, пожалуй, самая наглядная иллюстрация того, что выбор KDF определяется не задачей, а возрастом формата.

Артефакты без слотов ведут себя противоположно. Парольный получатель age — это строка `-> scrypt <соль> <log2 N>` в заголовке файла, `r`=8, `p`=1 фиксированы спецификацией, соль в 16 байт своя на каждый файл. Фактор 18 при `r`=8 — это 256 MiB и около секунды, что для однократного шифрования файла ровно правильно, а поменять его в уже записанном файле нельзя никак, кроме как перешифровав.

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

---

## Что здесь устареет первым

Рекомендованные параметры — быстрее всего. Числа OWASP пересматриваются регулярно, значения RFC 9106 зафиксированы в документе 2021 года и пересматриваться не будут вовсе, так что расхождение между ними со временем будет только расти. Перемерять надо на своей машине, а не переносить.

Статус SP 800-132. Решение о пересмотре принято в 2023 году, черновика на дату сверки нет. Когда он выйдет и одобрит память-жёсткую функцию, раздел про PBKDF2 и весь довод «только под FIPS» устареют разом — и это единственное место в статье, где вердикт может перевернуться целиком.

Умолчания дистрибутивов. Таблица выверена по исходникам пакетов на дату в шапке; строка RHEL 9 против RHEL 10 показывает, с какой скоростью это меняется. Проверяется одной командой — `grep ENCRYPT_METHOD /etc/login.defs` — и верить надо ей, а не статье.

Измерения. Таблица получена на одной виртуальной машине через OpenSSL 3.0.13; на другом железе изменятся все абсолютные значения, а отношение «PBKDF2 против scrypt по памяти» — нет, потому что оно следует из устройства функций.

---

## Источники

- [RFC 8018](https://www.rfc-editor.org/rfc/rfc8018.html) — PKCS #5 v2.1: назначение соли и рекомендация «at least eight octets» на её длину (§4.1), прирост стойкости на `log2(c)` бит и рекомендации по числу итераций со ссылкой на SP 800-132 (§4.2), определение PBKDF2 (§5.2)
- [RFC 7914](https://www.rfc-editor.org/rfc/rfc7914.html) — scrypt: `ROMix`, `BlockMix`, параметры `N`, `r`, `p`, тестовые векторы
- [RFC 9106](https://www.rfc-editor.org/rfc/rfc9106.html) — Argon2: различие 2d, 2i и 2id (§1, §3), процедура выбора параметров и оба рекомендованных набора (§4), размены «время против памяти» с числовыми оценками для каждого варианта (§7.2), формулировка рационального защитника (§7.3), итоговые рекомендации (§7.4)
- [NIST SP 800-132](https://csrc.nist.gov/pubs/sp/800/132/final) — действующая редакция 2010 года и пометка о планируемом пересмотре (11 мая 2023); черновика на дату сверки нет
- [NIST SP 800-63B, редакция 4](https://pages.nist.gov/800-63-4/sp800-63b.html) (26 августа 2025) — требования к хранению паролей: соль не короче 32 бит, отсылка к SP 800-132, повышение стоимости со временем, keyed-хеширование секретным ключом с хранением ключа в HSM или TEE
- [`crypt(5)` из libxcrypt](https://github.com/besser82/libxcrypt/blob/develop/doc/crypt.5) — три задачи соли, полный список поддерживаемых методов и их статус, диапазоны cost-факторов, предел bcrypt в 72 символа, «too low for modern hardware» про 5000 раундов sha512crypt
- [libxcrypt, `lib/crypt-yescrypt.c`](https://github.com/besser82/libxcrypt/blob/develop/lib/crypt-yescrypt.c) — отображение cost-фактора в `N` и `r`: умолчание 5, диапазон 1–11, при cost ≥ 3 берётся `r`=32
- [yescrypt: README](https://github.com/openwall/yescrypt) — почему память-жёсткость при низкой задержке почти не выигрывает у bcrypt, ROM, список дистрибутивов с yescrypt по умолчанию, версия 1.1.0
- [yescrypt: COMPARISON](https://github.com/openwall/yescrypt/blob/main/COMPARISON) — сравнение с scrypt и Argon2: 96 последовательных умножений на килобайт против 16, низкоуровневый параллелизм Argon2, TMTO-дружелюбность scrypt, собственные недостатки yescrypt
- [yescrypt: PARAMETERS](https://github.com/openwall/yescrypt/blob/main/PARAMETERS) — рекомендованные наборы для аутентификации: 2 MiB и 16 MiB, их соответствие bcrypt cost 2⁵ и 2⁸
- [N. Provos, D. Mazières, A Future-Adaptable Password Scheme](https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node4.html) (USENIX 1999) — eksblowfish: 4 KB постоянно изменяемой памяти, невозможность разделить S-боксы между вычислениями, ключ до 56 байт
- [C. Percival, Stronger Key Derivation via Sequential Memory-Hard Functions](https://www.tarsnap.com/scrypt/scrypt.pdf) (BSDCan'09) — доллар-секунды как единица стоимости, удешевление атаки при постоянном времени проверки, таблица 1 со стоимостью оборудования, состав её колонок («8 letters» против «8 chars») и оговорки к ней, обрезка длинных фраз в bcrypt на 55 символах
- [J. Bonneau, The Science of Guessing](http://www.umiacs.umd.edu/~tdumitra/courses/ENEE759D/Fall13/papers/Bonneau12.pdf) (IEEE S&P 2012) — оценка стойкости пользовательских паролей: менее 10 бит против онлайновой атаки, около 20 против оптимальной офлайновой
- [Password Hashing Competition](https://www.password-hashing.net/) — сроки конкурса, победитель (июль 2015) и четыре финалиста с особым упоминанием
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) — параметры Argon2id (19 MiB, `t`=2, `p`=1 и эквивалентные наборы), scrypt, bcrypt как легаси, 600 000 итераций PBKDF2 под требование FIPS, обе стратегии перца и рекомендация внутреннего варианта `bcrypt(base64(hmac-sha384(...)))` против password shucking
- [Fedora: yescrypt as default hashing method for shadow](https://fedoraproject.org/wiki/Changes/yescrypt_as_default_hashing_method_for_shadow) — четыре дефекта sha256crypt и sha512crypt, три довода в пользу yescrypt против Argon2, список затронутых пакетов
- [Arch Linux: changes to default password hashing algorithm](https://archlinux.org/news/changes-to-default-password-hashing-algorithm-and-umask-settings/) (22 сентября 2023) — смена умолчания в shadow 4.14.0 и довод про отсутствие Argon2 в libxcrypt
- [Debian, исходники shadow 1:4.19.3-2](https://sources.debian.org/src/shadow/) — `etc/login.defs` с `ENCRYPT_METHOD YESCRYPT`
- [CentOS Stream, shadow-utils](https://gitlab.com/redhat/centos-stream/rpms/shadow-utils) — `shadow-utils.login.defs`: `YESCRYPT` в ветке `c10s`, `SHA512` в ветке `c9s`
- [`login.defs(5)` в дереве shadow-utils](https://github.com/shadow-maint/shadow/tree/master/man/login.defs.d) — значения `ENCRYPT_METHOD` с умолчанием `SHA512`, диапазоны `YESCRYPT_COST_FACTOR` (1–11), `BCRYPT_MIN_ROUNDS` (4–31) и `SHA_CRYPT_MIN_ROUNDS`
- [`pam_unix(8)`](https://man7.org/linux/man-pages/man8/pam_unix.8.html) — умолчание берётся из `ENCRYPT_METHOD`, при отсутствии переменной — встроенный SHA512
- [glibc 2.38, `NEWS`](https://sourceware.org/git/?p=glibc.git;a=blob;f=NEWS;hb=refs/tags/glibc-2.38) — libcrypt больше не собирается по умолчанию, нужна опция `--enable-crypt`
- [glibc 2.39, объявление о релизе](https://lists.gnu.org/archive/html/info-gnu/2024-01/msg00017.html) — удаление libcrypt и опций `--enable-crypt`, отсылка к libxcrypt
- [cryptsetup, `man/common_options.adoc`](https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/man/common_options.adoc) — бенчмарк параметров при создании слота, минимальные и максимальные стоимости, диапазон памяти при бенчмарке от 64 MiB до 1 GiB
- [cryptsetup, `meson_options.txt`](https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/meson_options.txt) — умолчания сборки для LUKS2: `argon2id`, 2000 мс, 1 048 576 кБ памяти, 4 потока
- [restic, `internal/repository/crypto/kdf.go`](https://github.com/restic/restic/blob/master/internal/repository/crypto/kdf.go) и [`internal/repository/key.go`](https://github.com/restic/restic/blob/master/internal/repository/key.go) — scrypt с калибровкой при создании ключа, потолки 500 мс и 60 MiB (`memMiBytes`), параметры в файле ключа
- [Спецификация формата age](https://github.com/C2SP/C2SP/blob/main/age.md) — парольная строка `scrypt`, фиксированные `r`=8 и `p`=1, требование ограничить фактор при чтении, запрет смешивать её с другими получателями
- [age, `scrypt.go`](https://github.com/FiloSottile/age/blob/main/scrypt.go) — фактор 18 при записи и потолок 22 при чтении с комментариями о времени работы
- [`geli(8)`](https://man.freebsd.org/cgi/man.cgi?query=geli&sektion=8) — PKCS#5v2 для парольной части пользовательского ключа и подбор числа итераций под две секунды
- [`login.conf(5)` во FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=login.conf&sektion=5) — `passwd_format` с умолчанием `sha512` и пять допустимых значений: `des`, `md5`, `blf`, `sha256`, `sha512`
- [FreeBSD, `lib/libcrypt/crypt.c`](https://cgit.freebsd.org/src/tree/lib/libcrypt/crypt.c) — таблица `crypt_formats[]`: те же форматы плюс `nth` (NT-хеш, префикс `$3$`)
- [OpenSSL 3.2, release notes](https://openssl-library.org/news/openssl-3.2-notes/) — появление Argon2 в качестве KDF (23 ноября 2023)

---

## Связанные заметки

- [Обмен ключами: X25519, KEM, вывод ключей и привязка к транскрипту](./crypto-key-exchange.md) — вторая половина темы вывода ключей: HKDF выводит из уже случайного секрета и обязан быть быстрым, здесь всё наоборот
- [AEAD: аутентифицированное шифрование, nonce и лимиты](./crypto-aead.md) — обязательство по ключу, без которого шифрование по паролю не даёт внятного отказа при неверном пароле
- [AES-GCM против ChaCha20-Poly1305: как выбирать](./crypto-aes-vs-chacha20.md) — тот же приём: мерить на своём железе, а не переносить чужие числа, и тот же регуляторный контур в роли последнего аргумента
- [Пользователи, группы, PAM и systemd-logind](./linux-users-pam.md) — эксплуатационная сторона: `ENCRYPT_METHOD`, `/etc/login.defs`, формат поля в `/etc/shadow` и префиксы хешей
- [Резервное копирование: стратегия, инструменты, проверка восстановления](./backup-strategy.md) — где живёт пароль репозитория и чем `repokey` отличается от `keyfile`
- [Восстановление незагружающейся системы](./system-recovery.md) — заголовок LUKS со слотами как объект резервного копирования и что происходит, когда слоты пусты
- [Криптографические примитивы — план цикла статей](./crypto-knowledge-base-plan.md) — состав цикла, порядок статей и сквозные решения
