bcrypt.js реализует адаптированную версию алгоритма bcrypt для JavaScript-окружений. В основе лежит функция хеширования, специально разработанная для работы с паролями: она медленная по дизайну, использует соль и имеет параметр cost factor, который регулирует вычислительную сложность.
Ключевые свойства bcrypt:
Формат хеша включает все необходимые параметры:
$2a$12$N0r5...salt...hash
где 12 — это cost factor.
Однако bcrypt сам по себе не обеспечивает контроль целостности данных вне сценария хранения паролей. В задачах аутентификации, токенизации и защиты данных часто требуется дополнительный криптографический слой — 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 возникает в системах, где требуется одновременно:
Типовые сценарии:
Pepper — это секрет, который хранится отдельно от базы данных. В отличие от salt, он не сохраняется вместе с хешем.
Сначала пароль проходит через 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-хеш рассматривается как данные, которые могут быть изменены (например, при повреждении базы или атаке на слой хранения). Тогда 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 — для упаковки результата в токен.
Пример: генерация 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 в этой схеме выступает как стабильный компонент, привязанный к пользователю.
Комбинация может включать три уровня:
Пример:
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 };
}
HMAC слишком быстрый, что делает его непригодным для паролей.
Повторное применение bcrypt к результату не усиливает безопасность, но увеличивает стоимость вычислений без пользы.
Это полностью уничтожает смысл HMAC-усиления.
Изменение порядка HMAC и bcrypt приводит к несовместимости при верификации.
bcrypt является затратным алгоритмом:
Добавление HMAC практически не влияет на производительность, так как это быстрый hash-based механизм.
Основная нагрузка всегда создаётся bcrypt.
Комбинация bcrypt + HMAC чаще всего применяется в следующих слоях:
Такая модель создаёт разделение ответственности: