Оптимизация числа итераций PBKDF2

PBKDF2 (Password-Based Key Derivation Function 2) в контексте Stanford Javascript Crypto Library реализован как вычислительно затратная функция, основная цель которой — замедлить перебор паролей за счёт многократного применения псевдослучайной функции (обычно HMAC).

Ключевой параметр алгоритма — число итераций. Он определяет, сколько раз базовая криптографическая операция будет повторена при выводе ключа из пароля. В SJCL это значение передаётся как третий аргумент функции sjcl.misc.pbkdf2.

var key = sjcl.misc.pbkdf2(password, salt, iterations, keySize, prf);

Увеличение числа итераций линейно увеличивает время вычисления ключа, но экспоненциально усложняет атаки перебора при условии массового использования слабых паролей.


Влияние итераций на стойкость к перебору

PBKDF2 не улучшает энтропию пароля, но усложняет каждую попытку проверки кандидата. При атаке brute-force злоумышленник вынужден выполнять полный цикл вычислений для каждого предполагаемого пароля.

Пусть:

  • t — время одной итерации HMAC
  • n — количество итераций

Тогда стоимость проверки одного пароля:

T ≈ n * t

Таким образом, увеличение n в 10 раз увеличивает стоимость атаки в 10 раз, но одновременно замедляет и легитимную аутентификацию.


Баланс между безопасностью и производительностью

Выбор количества итераций — компромисс между:

  • устойчивостью к перебору
  • задержкой входа пользователя
  • нагрузкой на сервер или клиент

На клиентской стороне (браузерный JavaScript) этот баланс особенно критичен, поскольку вычисления выполняются в однопоточном окружении и напрямую влияют на отзывчивость интерфейса.

Типичные диапазоны:

  • устаревшие системы: 1 000 – 10 000
  • современные рекомендации: 100 000 – 600 000 и выше
  • интерактивные клиентские сценарии: часто 50 000 – 200 000

Однако фиксированные значения постепенно теряют актуальность из-за роста вычислительных мощностей GPU и специализированных атакующих систем.


Практическая настройка PBKDF2 в SJCL

Базовая реализация SJCL позволяет задавать параметры напрямую:

var derivedKey = sjcl.misc.pbkdf2(
    "user-password",
    sjcl.codec.hex.toBits("a3f1c9..."),
    150000,
    256,
    sjcl.misc.hmac
);

Здесь:

  • password — исходная строка
  • salt — случайная соль в битовом представлении
  • 150000 — число итераций
  • 256 — размер ключа в битах
  • prf — псевдослучайная функция (обычно HMAC-SHA256)

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


Измерение производительности и подбор параметров

Корректный выбор числа итераций невозможен без измерений. Основная цель — добиться времени вычисления, приемлемого для пользователя, но достаточного для усложнения перебора.

Практический подход:

  1. фиксируется целевое время вычисления (например, 200–500 мс)
  2. выполняется тестирование на целевых устройствах
  3. подбирается максимальное n, не превышающее порог

Пример бенчмарка:

function benchmarkPBKDF2(iterations) {
    var start = performance.now();

    sjcl.misc.pbkdf2(
        "test-password",
        sjcl.codec.utf8String.toBits("salt"),
        iterations,
        256
    );

    var end = performance.now();
    return end - start;
}

Поведение сильно зависит от:

  • архитектуры CPU
  • наличия аппаратного ускорения SHA
  • фоновой нагрузки
  • реализации JavaScript-движка

Адаптивное увеличение сложности

Фиксированное число итераций постепенно теряет эффективность, поэтому применяется адаптивный подход:

  • хранение параметра iterations вместе с хэшем пароля
  • увеличение значения при смене пароля или успешной аутентификации
  • миграция старых хэшей при входе пользователя

Структура хранения:

hash = PBKDF2(password, salt, iterations)

И вместе с этим:

{ salt, iterations, hash }

Такой подход позволяет эволюционно повышать стоимость атак без массового принудительного сброса паролей.


Ограничения клиентской среды JavaScript

PBKDF2 в SJCL выполняется синхронно, что приводит к блокировке основного потока браузера. При высоких значениях итераций это становится заметным:

  • задержки ввода
  • фризы интерфейса
  • увеличение времени отклика страницы

Частичное решение — выполнение криптографии в Web Workers:

// worker.js
self.onmess age = function(e) {
    var result = sjcl.misc.pbkdf2(
        e.data.password,
        e.data.salt,
        e.data.iterations,
        256
    );
    self.postMessage(result);
};

Это переносит нагрузку вне UI-потока, но не уменьшает общее время вычисления.


Влияние оборудования и современных ускорителей

Рост производительности GPU и появление специализированных ASIC существенно изменили модель угроз. PBKDF2 остаётся CPU-ориентированным алгоритмом, что делает его менее устойчивым по сравнению с memory-hard функциями, однако в SJCL он по-прежнему используется как базовый механизм.

Влияние аппаратных факторов:

  • SIMD-оптимизации ускоряют HMAC
  • параллельные вычисления уменьшают стоимость перебора
  • современные CPU могут выполнять сотни тысяч итераций за миллисекунды

Это приводит к необходимости регулярного пересмотра параметра iterations.


Практические диапазоны выбора

Для реальных систем на базе SJCL применяются следующие ориентиры:

  • минимально допустимый уровень: 100 000 итераций
  • безопасный средний уровень: 200 000 – 500 000
  • высоконагруженные или чувствительные системы: от 500 000 до 1 000 000

Однако каждый диапазон должен корректироваться под конкретную инфраструктуру и требования к UX.


Ошибки при настройке итераций

Частые проблемы:

  • использование фиксированного малого значения (1 000 – 10 000)
  • отсутствие соли при PBKDF2
  • игнорирование измерений производительности
  • одинаковые параметры для серверов и слабых клиентских устройств
  • отсутствие миграции старых хэшей

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


Совместимость и миграция параметров

Изменение числа итераций требует обратной совместимости:

  • старые хэши продолжают проверяться с сохранённым iterations
  • новые хэши генерируются с увеличенным значением
  • при успешном входе происходит пересчёт ключа с новым параметром

Типовая логика:

if (verify(password, hash, salt, oldIterations)) {
    newHash = pbkdf2(password, salt, newIterations, 256);
}

Это обеспечивает постепенное усиление криптографической стойкости без массового сброса данных.


Динамическая калибровка под устройство

Некоторые системы применяют адаптацию:

  • измерение скорости PBKDF2 при первом запуске
  • установка итераций, соответствующих фиксированному времени (например, 250 мс)
  • хранение профиля производительности устройства

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