openjdk.ruOpenJDK на русском

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) необходимо увеличивать, чтобы сохранить уровень безопасности.