PBKDF2: стандарт NIST и совместимость

PBKDF2 (Password-Based Key Derivation Function 2) — одна из наиболее широко используемых функций формирования ключей на основе пароля. Формально она описана в рекомендациях NIST SP 800-132 и RFC 8018 (PKCS #5 v2.1). Основная идея PBKDF2 заключается в многократном применении криптографической хеш-функции к паролю вместе со случайной солью для получения стойкого криптографического ключа.

NIST SP 800-132 определяет PBKDF2 как рекомендованный механизм для защиты паролей и генерации ключевого материала, при этом акцент делается на:

  • обязательное использование криптографически стойкой соли;
  • достаточное количество итераций (work factor);
  • возможность выбора псевдослучайной функции (PRF), чаще всего HMAC-SHA256;
  • защиту от атак перебора за счёт увеличения вычислительной стоимости.

PBKDF2 не является алгоритмом хеширования паролей в узком смысле, а относится к классу key derivation functions (KDF), предназначенных для преобразования пароля в криптографический ключ фиксированной длины.


Формальная модель работы PBKDF2

PBKDF2 строится на основе итеративного применения функции HMAC:

DK = PBKDF2(PRF, Password, Salt, c, dkLen)

где:

  • PRF — псевдослучайная функция (обычно HMAC-SHA256)
  • Password — исходный пароль
  • Salt — криптографическая соль
  • c — количество итераций
  • dkLen — длина итогового ключа

Упрощённо процесс можно описать как многократное повторение HMAC над результатами предыдущих вычислений, что делает каждый следующий шаг дороже по времени.


Итерации и вычислительная сложность

Ключевой параметр PBKDF2 — количество итераций c. Именно он определяет устойчивость к перебору.

  • 1 000 итераций — устаревший минимум
  • 10 000–100 000 — типичный диапазон для современных систем
  • 500 000+ — используется в высокозащищённых системах

Рост числа итераций линейно увеличивает время вычисления, но не увеличивает требования к памяти.


Свойства PBKDF2 в контексте NIST

NIST SP 800-132 подчёркивает несколько важных характеристик:

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

Однако в более поздних рекомендациях NIST (в частности SP 800-63B) отмечается, что PBKDF2 допустим, но не является наиболее устойчивым вариантом по сравнению с memory-hard функциями (например, scrypt или Argon2).


PBKDF2 в Node.js и экосистеме JavaScript

В Node.js PBKDF2 реализован через модуль crypto:

const crypto = require('crypto');

crypto.pbkdf2('password', 'salt', 100000, 64, 'sha256', (err, derivedKey) => {
  if (err) throw err;
  console.log(derivedKey.toString('hex'));
});

Синхронная версия:

const key = crypto.pbkdf2Sync('password', 'salt', 100000, 64, 'sha256');

Эта реализация соответствует стандарту PKCS #5 v2.1 и совместима с PBKDF2-реализациями в других языках.


Совместимость PBKDF2 и bcrypt.js

bcrypt.js реализует алгоритм bcrypt, который принципиально отличается от PBKDF2:

  • PBKDF2 основан на HMAC (обычно SHA-256)
  • bcrypt основан на EksBlowfish (специально модифицированный Blowfish)
  • PBKDF2 регулируется числом итераций
  • bcrypt регулируется параметром cost factor (экспоненциальная сложность)

Несмотря на различия, оба алгоритма решают одну задачу — безопасное хеширование паролей.

Совместимость между ними отсутствует на уровне формата:

  • хеш PBKDF2 не может быть проверен через bcrypt.js
  • bcrypt-хеш не может быть проверен через PBKDF2

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


Отличия моделей вычислительной нагрузки

PBKDF2:

  • CPU-bound (зависит от процессора)
  • не требует значительной памяти
  • легко масштабируется по итерациям

bcrypt:

  • CPU-bound + частично memory-bound
  • встроенное ограничение скорости через cost factor
  • фиксированная длина блока и внутренняя структура Blowfish

В современных системах это приводит к разной устойчивости к специализированным атакам:

  • PBKDF2 быстрее реализуется на GPU
  • bcrypt сложнее оптимизируется на GPU из-за структуры Blowfish

Формат хранения данных

PBKDF2 обычно хранится в виде:

iterations:salt:derivedKey

или в Base64/hex кодировке.

bcrypt использует собственный формат:

$2b$12$<salt><hash>

Где:

  • $2b$ — версия алгоритма
  • 12 — cost factor
  • salt встроен в строку
  • хеш фиксированной длины

Эта разница делает невозможной прямую интероперабельность.


Практические аспекты выбора между PBKDF2 и bcrypt.js

PBKDF2 часто применяется в следующих сценариях:

  • совместимость со стандартами NIST
  • интеграция с существующими криптосистемами
  • генерация ключей шифрования (AES, HMAC)
  • использование встроенных библиотек без сторонних зависимостей

bcrypt.js применяется:

  • в системах хранения паролей
  • в веб-приложениях с высокой частотой логинов
  • в проектах, где важна проверенная устойчивость bcrypt как password hashing algorithm

Безопасность и современные рекомендации

PBKDF2 остаётся допустимым, но его ограничения связаны с архитектурой:

  • отсутствие memory-hard свойств
  • высокая параллелизуемость атак
  • зависимость только от CPU-итераций

bcrypt частично компенсирует эти недостатки за счёт структуры EksBlowfish, но также не является наиболее современным решением.

В современных криптографических практиках всё чаще рассматриваются альтернативы:

  • scrypt (memory-hard)
  • Argon2 (winner Password Hashing Competition)

Тем не менее PBKDF2 сохраняет статус стандарта NIST и широко используется в корпоративных и государственных системах.


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

В некоторых архитектурах PBKDF2 и bcrypt используются совместно:

  • PBKDF2 для генерации ключей шифрования данных
  • bcrypt для хранения паролей пользователей

Такой подход позволяет разделить:

  • криптографическое управление ключами
  • аутентификационную подсистему

При этом важно учитывать, что миграция между ними невозможна без повторного хеширования паролей в момент входа пользователя.


Ограничения PBKDF2 в сравнении с bcrypt.js

  • линейная масштабируемость атак на GPU
  • отсутствие встроенной адаптации к современному железу
  • необходимость ручного подбора числа итераций
  • отсутствие встроенной версии алгоритма (в отличие от 2a, 2b у bcrypt)

bcrypt.js, несмотря на устаревающую архитектуру, предоставляет более унифицированный формат хранения и проверки паролей, что делает его удобнее в веб-среде.