JEP draft: Argon2 Password Hashing Algorithm (Preview)
Алгоритм хеширования паролей Argon2, версия Preview (предварительная версия)
| Ответственный | Artur Barashev |
| Тип | Feature |
| Область | SE |
| Статус | Submitted |
| Компонент | security-libs / javax.crypto |
| Обсуждение | security dash dev at openjdk dot org |
| Трудоёмкость | M |
| Длительность | M |
| Связан с | JEP 510: Key Derivation Function API |
| Рецензенты | Sean Mullan |
| Создан | 2026/02/03 15:26 |
| Обновлён | 2026/08/14 14:11 |
| Задача | 8377081 |
Аннотация
Повысить безопасность Java-приложений за счёт реализации алгоритма хеширования паролей Argon2. Argon2 дополнительно защищает от атак, подбирающих пароль перебором, и стал победителем Password Hashing Competition 2015 года. Это API в статусе Preview.
Цели
- Предоставить реализацию javax.crypto.KDF для варианта Argon2id алгоритма Argon2, описанного в RFC 9106.
Что не является целью
- Реализация вариантов Argon2d и Argon2i алгоритма Argon2 не является целью.
Мотивация
Платформа Java должна лучше поддерживать алгоритмы хеширования паролей помимо PBKDF2. PBKDF2 ограничен вычислениями и требует мало памяти. Поэтому он хорошо ложится на массово-параллельное оборудование и уязвим для офлайн-взлома с помощью графических процессоров (GPU) или специализированных интегральных схем (ASIC).
Argon2 — алгоритм с высокими требованиями к памяти (memory-hard). Высокие требования к памяти повышают стоимость массового подбора паролей, потому что ограничивают возможности злоумышленников использовать специализированное оборудование. Например, если каждая попытка подбора требует 64 МиБ памяти, то устройство с 8 ГиБ доступной памяти может выполнять параллельно лишь около 128 попыток, а не тысячи или миллионы.
Платформа Java должна предоставлять стандартный алгоритм хеширования паролей с высокими требованиями к памяти. Другие криптографические библиотеки, например OpenSSL и BouncyCastle, уже поддерживают Argon2.
Описание
Argon2 определяет три варианта:
- Argon2d обращается к памяти в зависимости от данных и лучше защищает от атак с компромиссом между временем и памятью (time-memory trade-off, TMTO).
- Argon2i обращается к памяти независимо от данных и лучше защищает от атак по сторонним каналам.
- Argon2id — гибридная схема: в первой половине первого прохода по памяти он работает как Argon2i, а в остальное время — как Argon2d. Согласно RFC 9106, «Argon2id MUST be supported by any implementation of this document» («Argon2id ДОЛЖЕН поддерживаться любой реализацией этого документа»).
Этот JEP предлагает реализовать Argon2id и тем самым обеспечить баланс между устойчивостью к атакам по сторонним каналам и защитой от TMTO. Алгоритм будет реализован как сервис KDF провайдера SunJCE в соответствии с API, представленным в JEP 510.
У Argon2id есть несколько входных параметров, которыми можно настроить алгоритм. Эти параметры можно подобрать с учётом требований к безопасности вашего приложения и системы, в которой оно работает. Параметры памяти (m, в КиБ), числа итераций (t) и степени параллелизма (p, число потоков выполнения) позволяют приложениям настраивать вычислительные затраты и затраты памяти в соответствии с требованиями развёртывания. Параметры соли (salt, обычно 16 байт) и необязательного секрета (k) часто используются в алгоритмах хеширования паролей, чтобы затруднить атаки подбора паролей. Среди других параметров — необязательные связанные данные (x), версия алгоритма (по умолчанию V13) и длина тега (tagLen, обычно 32 байта), то есть длина итогового хеша.
Это API в статусе Preview, по умолчанию отключённый
Чтобы использовать этот API в JDK 28, необходимо включить API в статусе Preview:
-
Скомпилируйте программу с
javac --release 28 --enable-preview Main.javaи запускайте её сjava --enable-preview Main; или -
При использовании средства запуска исходного кода запускайте программу с
java --enable-preview Main.java; или -
При использовании jshell запускайте его с
jshell --enable-preview.
Интеграция с KDF API
Argon2id доступен как стандартный алгоритм KDF с именем "Argon2id". Приложения получают экземпляр так:
KDF.getInstance("Argon2id");
Класс Argon2ParameterSpec
Класс javax.crypto.spec.Argon2ParameterSpec задаёт параметры алгоритма Argon2:
public final class Argon2ParameterSpec implements AlgorithmParameterSpec {
// input parameters
int memoryKiB();
int iterations();
int parallelism();
int tagLen();
byte[] secret();
byte[] associatedData();
Argon2ParameterSpec.Version version();
// password and salt
byte[] password();
byte[] salt();
// builder
static Argon2ParameterSpec.Builder newBuilder();
}
Класс Argon2ParameterSpec.Builder
Вложенный класс javax.crypto.spec.Argon2ParameterSpec.Builder формирует входные данные Argon2:
public static final class Builder {
Builder memoryKiB(int m);
Builder memoryPowerOfTwo(int mPower);
Builder iterations(int t);
Builder parallelism(int p);
Builder tagLen(int tagLen);
Builder secret(byte[] k);
Builder associatedData(byte[] x);
Builder version(Argon2ParameterSpec.Version ver);
Argon2ParameterSpec build(byte[] salt, byte[] password);
Argon2ParameterSpec build(byte[] salt, char[] password, Charset cs);
};
Класс Argon2ParameterSpec.Version
Вложенный класс javax.crypto.spec.Argon2ParameterSpec.Version инкапсулирует версии Argon2:
public enum Version {
V10(0x10), // version 1.0, not supported by SunJCE provider
V13(0x13); // RFC 9106 version, supported by SunJCE provider
};
Пример кода
Этот пример основан на универсально безопасном варианте №2, описанном в разделе 4 RFC 9106.
Сначала создаём Argon2ParameterSpec.Builder с 64 МиБ оперативной памяти, 3 итерациями, 4 параллельно обрабатываемыми полосами (lanes) и длиной тега 256 бит. Примечание: метод memoryPowerOfTwo задаёт параметр памяти равным 2ⁿ КиБ, где n — значение, переданное методу; поэтому в этом примере получается 2¹⁶ = 65536 КиБ, или 64 МиБ.
Argon2ParameterSpec.Builder builder = Argon2ParameterSpec.newBuilder()
.memoryPowerOfTwo(16).iterations(3).parallelism(4).tagLen(32);
Затем вызываем метод build и задаём случайную 128-битную соль и пароль, который нужно захешировать:
Argon2ParameterSpec param = builder.build(randomSalt128, password);
Затем получаем экземпляр KDF для Argon2id и генерируем хеш пароля в виде массива байтов:
KDF k = KDF.getInstance("Argon2id");
byte[] passwordHash = k.deriveData(param);
Результат можно также сгенерировать в виде SecretKey:
SecretKey key = k.deriveKey("Generic", param);
Миграция с PBKDF2
PBKDF2 реализован через javax.crypto.SecretKeyFactory, а этот JEP предлагает реализовать Argon2id через API javax.crypto.KDF. Java-приложениям, которые захотят перейти с PBKDF2 на Argon2id, придётся изменить код, чтобы перейти на KDF API.
Переход на Argon2id требует изменений в коде, но для миграции хранимых учётных данных нужна ещё и стратегия работы с данными и развёртывания, поскольку результаты PBKDF2 и Argon2id не взаимозаменяемы. Рекомендуемые стратегии:
- Постепенная миграция во время аутентификации пользователей.
- Повторное хеширование существующих хешей PBKDF2 с помощью Argon2id (двойное хеширование).
Альтернативы
OWASP Password Storage Cheat Sheet перечисляет три современных алгоритма хеширования, которые стоит рассматривать при выборе алгоритма хеширования паролей: Argon2id, scrypt и bcrypt. Из этих 3 предпочтительным вариантом там рекомендован Argon2id. Вот дополнительные доводы, почему мы выбрали Argon2id, а не scrypt и bcrypt:
- bcrypt широко распространён, но по современным меркам не предъявляет высоких требований к памяти и, как правило, хуже, чем Argon2id, противостоит современному взлому на GPU.
- scrypt предъявляет высокие требования к памяти, но допускает более эффективные атаки TMTO, чем Argon2id.
Тестирование
- Модульные тесты подтвердят, что реализации соответствуют спецификациям KDF API, включая граничные случаи, такие как недопустимые входные параметры, граничные значения и неподдерживаемые операции; будет также добавлена проверка по тестовым векторам с известными ответами из RFC 9106.
- Тесты совместимости с реализациями других поставщиков, включая BouncyCastle, но не ограничиваясь им, подтвердят, что наша реализация Argon2 хорошо работает с другими.
Риски и допущения
- Высокие требования Argon2 к памяти рассчитаны на то, что JVM сможет выделить запрошенную память; в противном случае хеширование пароля с помощью Argon2 завершится ошибкой.
- Эффекты среды выполнения (например, работа сборщика мусора) могут вносить разброс во время выполнения; вызывающему коду не следует считать время выполнения постоянным.
- Стойкость не фиксирована. Она полностью зависит от параметров алгоритма. Если используются слабые параметры, Argon2 становится слабым. При уменьшении памяти (m) число итераций (t) необходимо увеличивать, чтобы сохранить уровень безопасности.