Выбор между PBKDF2 и HKDF

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

PBKDF2 (Password-Based Key Derivation Function 2) решает проблему слабости паролей. Любой пользовательский пароль обладает низкой энтропией, поэтому прямое использование его как ключа недопустимо. PBKDF2 увеличивает стоимость перебора за счёт итераций хеширования.

В Web Crypto API он доступен через crypto.subtle.deriveKey и deriveBits.

const enc = new TextEncoder();

const passwordKey = await crypto.subtle.importKey(
  "raw",
  enc.encode("user-password"),
  "PBKDF2",
  false,
  ["deriveKey"]
);

const salt = crypto.getRandomValues(new Uint8Array(16));

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

Ключевые характеристики PBKDF2:

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

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

HKDF: расширение ключевого материала

HKDF (HMAC-based Extract-and-Expand Key Derivation Function) не предназначен для паролей. Его задача — преобразовать уже безопасный секрет в несколько независимых ключей.

В Web Crypto API HKDF также используется через deriveKey или deriveBits.

const enc = new TextEncoder();

const inputKeyMaterial = await crypto.subtle.importKey(
  "raw",
  enc.encode("shared-secret"),
  "HKDF",
  false,
  ["deriveKey"]
);

const salt = crypto.getRandomValues(new Uint8Array(16));

const derivedKey = await crypto.subtle.deriveKey(
  {
    name: "HKDF",
    salt: salt,
    info: enc.encode("encryption key"),
    hash: "SHA-256"
  },
  inputKeyMaterial,
  {
    name: "AES-GCM",
    length: 256
  },
  false,
  ["encrypt", "decrypt"]
);

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

  • Extract — формирование псевдослучайного ключа из входного материала с помощью HMAC
  • Expand — генерация необходимого количества ключевого материала с использованием контекстной информации (info)

Свойства HKDF:

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

Сравнение применимости

PBKDF2 и HKDF решают принципиально разные задачи, несмотря на схожий интерфейс Web Crypto API.

PBKDF2:

  • вход: пароль
  • цель: защита от перебора
  • механизм: итерации хеша
  • основное применение: хранение паролей, derivation ключа из пользовательского ввода

HKDF:

  • вход: криптографический секрет
  • цель: генерация нескольких ключей
  • механизм: HMAC extract/expand
  • основное применение: протоколы обмена ключами, TLS-подобные схемы, ECDH результат

Ошибки выбора в архитектуре

Использование PBKDF2 вместо HKDF приводит к избыточным вычислениям и отсутствию логического разделения ключей. Например, попытка выводить несколько ключей из одного пароля через PBKDF2 с разными salt не создаёт корректной криптографической изоляции контекста.

Использование HKDF вместо PBKDF2 приводит к критической уязвимости: отсутствие защиты от перебора пароля. Любой атакующий получает возможность мгновенно проверять гипотезы о пароле.

Контекстное разделение ключей через HKDF

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

const enc = new TextEncoder();

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

const encKey = await crypto.subtle.deriveKey(
  {
    name: "HKDF",
    salt: new Uint8Array(16),
    info: enc.encode("encryption"),
    hash: "SHA-256"
  },
  baseKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

const macKey = await crypto.subtle.deriveKey(
  {
    name: "HKDF",
    salt: new Uint8Array(16),
    info: enc.encode("authentication"),
    hash: "SHA-256"
  },
  baseKey,
  { name: "HMAC", hash: "SHA-256" },
  false,
  ["sign", "verify"]
);

Разделение через info обеспечивает криптографическую независимость ключей при сохранении одного источника секретности.

Практическая модель выбора

PBKDF2 используется в момент, когда:

  • входной материал непредсказуем и слаб (пароль)
  • требуется замедление атак перебора
  • важна совместимость с устаревшими системами

HKDF используется в момент, когда:

  • входной материал уже безопасен (ECDH, предварительно установленный ключ)
  • требуется генерация нескольких ключей
  • необходима изоляция контекстов использования

Итоговая инженерная логика применения

В Web Crypto API оба алгоритма находятся в одной функциональной категории, но выполняют разные роли в криптографическом конвейере: PBKDF2 превращает небезопасное в пригодное, HKDF структурирует уже безопасное в системно разделённое.