Комбинирование bcrypt с HMAC

bcrypt.js реализует адаптированную версию алгоритма bcrypt для JavaScript-окружений. В основе лежит функция хеширования, специально разработанная для работы с паролями: она медленная по дизайну, использует соль и имеет параметр cost factor, который регулирует вычислительную сложность.

Ключевые свойства bcrypt:

  • встроенная случайная соль (salt)
  • адаптивная сложность (work factor)
  • устойчивость к перебору за счёт замедления вычислений
  • детерминированность результата при одинаковых входных данных и соли

Формат хеша включает все необходимые параметры:

$2a$12$N0r5...salt...hash

где 12 — это cost factor.

Однако bcrypt сам по себе не обеспечивает контроль целостности данных вне сценария хранения паролей. В задачах аутентификации, токенизации и защиты данных часто требуется дополнительный криптографический слой — HMAC.


HMAC как механизм криптографической подписи

HMAC (Hash-based Message Authentication Code) — это механизм, который использует криптографическую хеш-функцию вместе с секретным ключом для обеспечения:

  • целостности данных
  • аутентичности источника
  • защиты от подмены

В отличие от bcrypt, HMAC не предназначен для хранения паролей. Он используется для подписания сообщений:

const crypto = require('crypto');

const signature = crypto
  .createHmac('sha256', secretKey)
  .update(data)
  .digest('hex');

HMAC быстрый, поэтому не подходит для защиты паролей, но идеален для подписи токенов и проверяемых структур данных.


Причины комбинирования bcrypt и HMAC

Комбинация bcrypt и HMAC возникает в системах, где требуется одновременно:

  • защита пароля от перебора (bcrypt)
  • защита результата или структуры данных от подмены (HMAC)
  • разделение ответственности между хранением и верификацией

Типовые сценарии:

  • хранение пароля + подпись записи пользователя
  • защита токенов, производных от bcrypt-хеша
  • добавление “pepper” на уровне HMAC
  • контроль целостности хранилища паролей

Использование HMAC как pepper в связке с bcrypt

Pepper — это секрет, который хранится отдельно от базы данных. В отличие от salt, он не сохраняется вместе с хешем.

Подход: HMAC → bcrypt

Сначала пароль проходит через HMAC, затем результат хешируется bcrypt:

const bcrypt = require('bcryptjs');
const crypto = require('crypto');

const PEPPER = process.env.PEPPER_KEY;

function hashPassword(password) {
  const hmac = crypto
    .createHmac('sha256', PEPPER)
    .update(password)
    .digest('hex');

  return bcrypt.hashSync(hmac, 12);
}

function verifyPassword(password, storedHash) {
  const hmac = crypto
    .createHmac('sha256', PEPPER)
    .update(password)
    .digest('hex');

  return bcrypt.compareSync(hmac, storedHash);
}

Свойства схемы:

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

Важный нюанс: порядок операций

Существуют два распространённых варианта:

1. HMAC → bcrypt (рекомендуемый)

  • сначала нормализация через HMAC
  • затем медленное хеширование bcrypt

Плюс:

  • bcrypt защищает от перебора
  • HMAC добавляет секретный слой

2. bcrypt → HMAC (редко используется)

  • сначала bcrypt-хеш
  • затем подпись HMAC

Минус:

  • bcrypt уже самодостаточен
  • HMAC не добавляет защиты пароля, только целостность строки

HMAC для защиты bcrypt-хеша как объекта

В некоторых системах bcrypt-хеш рассматривается как данные, которые могут быть изменены (например, при повреждении базы или атаке на слой хранения). Тогда HMAC используется как подпись записи:

const crypto = require('crypto');

function signHash(bcryptHash, secret) {
  return crypto
    .createHmac('sha256', secret)
    .update(bcryptHash)
    .digest('hex');
}

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

{
  passwordHash: "$2a$12$...",
  signature: "9f3a..."
}

Проверка:

function verifyIntegrity(hash, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(hash)
    .digest('hex');

  return expected === signature;
}

Такая схема полезна, когда требуется обнаруживать:

  • несанкционированные изменения базы
  • повреждение данных
  • инсайдерские модификации

Использование bcrypt внутри HMAC-основанных токенов

Иногда bcrypt используется как источник медленного значения, а HMAC — для упаковки результата в токен.

Пример: генерация session token

const bcrypt = require('bcryptjs');
const crypto = require('crypto');

function generateSessionToken(userId, passwordHash, secret) {
  const base = `${userId}:${passwordHash}:${Date.now()}`;

  return crypto
    .createHmac('sha256', secret)
    .update(base)
    .digest('hex');
}

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


Усиление безопасности через многоуровневое хеширование

Комбинация может включать три уровня:

  1. HMAC (pepper)
  2. bcrypt (salt + cost)
  3. HMAC (подпись результата)

Пример:

const bcrypt = require('bcryptjs');
const crypto = require('crypto');

const PEPPER = process.env.PEPPER;

function doubleProtectedHash(password) {
  const first = crypto
    .createHmac('sha256', PEPPER)
    .update(password)
    .digest('hex');

  const bcryptHash = bcrypt.hashSync(first, 12);

  const signature = crypto
    .createHmac('sha256', PEPPER)
    .update(bcryptHash)
    .digest('hex');

  return { bcryptHash, signature };
}

Ключевые угрозы и защитные эффекты

Утечка базы данных

  • bcrypt защищает от прямого перебора
  • HMAC добавляет секретный слой, если используется pepper

Rainbow table атаки

  • bcrypt с солью полностью блокирует эффективность предвычисленных таблиц

Подмена записей

  • HMAC поверх bcrypt-хеша выявляет любые изменения строки

Компрометация приложения

  • при утечке кода без secret key HMAC теряет силу, но bcrypt остаётся устойчивым

Ошибки при комбинировании bcrypt и HMAC

Использование HMAC как замены bcrypt

HMAC слишком быстрый, что делает его непригодным для паролей.

Двойное хеширование bcrypt

Повторное применение bcrypt к результату не усиливает безопасность, но увеличивает стоимость вычислений без пользы.

Хранение pepper в базе данных

Это полностью уничтожает смысл HMAC-усиления.

Неправильная проверка порядка

Изменение порядка HMAC и bcrypt приводит к несовместимости при верификации.


Производительность и стоимость вычислений

bcrypt является затратным алгоритмом:

  • cost factor 10–14 — типичный диапазон
  • рост стоимости экспоненциальный

Добавление HMAC практически не влияет на производительность, так как это быстрый hash-based механизм.

Основная нагрузка всегда создаётся bcrypt.


Практическая архитектура в реальных системах

Комбинация bcrypt + HMAC чаще всего применяется в следующих слоях:

  • bcrypt.js — хранение паролей
  • HMAC — подпись токенов сессии
  • HMAC (pepper) — дополнительная защита входных данных перед bcrypt
  • HMAC — контроль целостности базы

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

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