PBKDF2, HKDF и производные ключи

Зачем вообще нужны PBKDF2 и HKDF

В прикладной криптографии редко используется «сырой» пароль или исходный ключ напрямую. Причина проста: такие значения обычно не обладают достаточной энтропией и плохо подходят для современных атакующих моделей.

Производные ключи решают сразу несколько задач:

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

В JavaScript-экосистеме, особенно при работе с JWT/JWE/JWS через библиотеку jose, эти механизмы реализуются через WebCrypto API (SubtleCrypto), который jose использует как основной криптографический backend.


PBKDF2: преобразование пароля в ключ

Модель работы PBKDF2

PBKDF2 (Password-Based Key Derivation Function 2) предназначен для получения стойкого ключа из пароля за счёт вычислительно дорогой функции:

K = (P, S, c, dkLen)

где:

  • P — пароль
  • S — соль (salt)
  • c — количество итераций
  • dkLen — длина производного ключа

Ключевой смысл PBKDF2 — замедлить перебор паролей за счёт большого числа итераций.


Использование PBKDF2 через WebCrypto (и jose runtime)

В jose напрямую PBKDF2 не оборачивается отдельной утилитой — используется стандартный crypto.subtle.deriveKey.

Пример получения ключа AES-GCM из пароля:

const enc = new TextEncoder();

const password = 'user-password';
const salt = crypto.getRandomValues(new Uint8Array(16));

const baseKey = await crypto.subtle.importKey(
  'raw',
  enc.encode(password),
  'PBKDF2',
  false,
  ['deriveKey']
);

const derivedKey = await crypto.subtle.deriveKey(
  {
    name: 'PBKDF2',
    salt,
    iterations: 100000,
    hash: 'SHA-256'
  },
  baseKey,
  {
    name: 'AES-GCM',
    length: 256
  },
  false,
  ['encrypt', 'decrypt']
);

Практические особенности PBKDF2

  • Итерации критически важны: чем выше число, тем сложнее атака перебором
  • Соль обязательна: без неё одинаковые пароли дают одинаковые ключи
  • Хеш-функция обычно SHA-256 или SHA-512
  • PBKDF2 считается устаревающим в новых системах, но всё ещё широко используется

HKDF: безопасное расширение ключей

Концепция HKDF

HKDF (HMAC-based Key Derivation Function) используется не для паролей, а для расширения уже существующего криптографического ключа.

Он состоит из двух этапов:

  • Extract — извлечение псевдослучайного ключа
  • Expand — генерация нужного количества ключевого материала

Формально:

(IKM, salt, info) = ((IKM, salt), info)

где:

  • IKM — input keying material (исходный ключ)
  • salt — дополнительная случайность
  • info — контекст (очень важный параметр)

Почему HKDF используется в jose

Библиотека jose активно работает с:

  • JWE (шифрование JWT)
  • JWS (подпись JWT)
  • JWK (ключи)

В этих схемах часто требуется:

  • разделять ключи по назначению
  • получать несколько ключей из одного master key
  • изолировать контексты (например, “encrypt”, “sign”, “derive”)

HKDF идеально подходит для этого сценария.


Использование HKDF через WebCrypto

const enc = new TextEncoder();

const masterKey = await crypto.subtle.importKey(
  'raw',
  enc.encode('master-secret'),
  'HKDF',
  false,
  ['deriveKey']
);

const derivedKey = await crypto.subtle.deriveKey(
  {
    name: 'HKDF',
    hash: 'SHA-256',
    salt: crypto.getRandomValues(new Uint8Array(16)),
    info: enc.encode('jose-encryption-context')
  },
  masterKey,
  {
    name: 'AES-GCM',
    length: 256
  },
  false,
  ['encrypt']
);

Роль параметра info

info — это ключевая особенность HKDF. Он позволяет привязать ключ к конкретному контексту использования.

Примеры значений:

  • "jose.jwt.sign"
  • "jose.jwt.encrypt"
  • "session-key-v1"

Это защищает от атак повторного использования ключей в разных сценариях.


Сравнение PBKDF2 и HKDF

Характеристика PBKDF2 HKDF
Вход пароль криптографический ключ
Основная цель защита от перебора расширение ключей
Использование соли обязательно рекомендуется
Контекст (info) отсутствует обязательный элемент дизайна
Производительность медленный быстрый
Современность устаревает актуальный стандарт

Как jose использует производные ключи

В экосистеме jose ключи редко создаются «вручную». Вместо этого:

  • ключи импортируются из JWK
  • или генерируются через WebCrypto
  • или производятся через deriveKey (PBKDF2/HKDF)

Пример типичного сценария:

  1. Пароль пользователя → PBKDF2 → master key
  2. master key → HKDF → ключи для разных операций
  3. ключи → используются в JWE/JWS

Использование в JWE (шифрование JWT)

В процессе JWE часто требуется:

  • Content Encryption Key (CEK)
  • Key Encryption Key (KEK)

HKDF может использоваться для детерминированного вывода этих ключей из одного источника.

Пример концептуальной схемы:

  • master secret
  • HKDF(“encrypt”) → CEK
  • HKDF(“wrap”) → KEK

Это обеспечивает изоляцию криптографических ролей.


Ошибки при работе с PBKDF2 и HKDF

1. Малое число итераций PBKDF2

Слишком маленькое значение делает систему уязвимой к перебору.

2. Отсутствие соли

Повторяемость ключей при одинаковых паролях.

3. Игнорирование info в HKDF

Убирает контекстную защиту и превращает HKDF в менее безопасную конструкцию.

4. Повторное использование ключей

Один ключ для шифрования и подписи — критическая ошибка.


Практика в архитектуре JWT-систем

При построении системы на jose важно разделять уровни:

  • пароль → PBKDF2 → ключ пользователя
  • ключ пользователя → HKDF → служебные ключи
  • служебные ключи → JWS/JWE операции

Такая структура обеспечивает:

  • изоляцию компрометации
  • масштабируемость
  • контроль жизненного цикла ключей

Криптографическая устойчивость и выбор алгоритмов

PBKDF2 остаётся совместимым стандартом, но в современных системах часто рассматриваются альтернативы:

  • Argon2 (вне WebCrypto)
  • scrypt (частично поддерживается через библиотеки)

HKDF же остаётся базовым строительным блоком современных протоколов (TLS 1.3, JOSE-экосистема, WebAuthn-подобные схемы).


Итоговая архитектурная модель использования

В системах, построенных на jose, производные ключи обычно формируют цепочку:

  • пользовательский секрет → PBKDF2
  • корневой ключ → HKDF
  • прикладные ключи → JWS / JWE операции

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