В библиотеке bcrypt.js процесс хеширования пароля базируется на использовании встроенной соли (salt), которая делает каждый хеш уникальным даже при одинаковых входных значениях. Однако в реальных системах часто применяется дополнительный уровень защиты — pepper, серверный секрет, который не хранится в базе данных и добавляется к паролю до хеширования.
Salt (соль) Соль — это случайная строка, которая генерируется для каждого пароля отдельно и хранится вместе с хешем в базе данных. Основная цель соли — защита от радужных таблиц и обеспечение уникальности хешей.
Pepper (перец) Pepper — это глобальный секрет, одинаковый для всей системы, который добавляется к паролю перед хешированием. В отличие от соли, pepper:
Алгоритм bcrypt уже включает соль как часть своей конструкции:
hash = bcrypt(password + salt)
При использовании pepper формула изменяется:
hash = bcrypt(password + pepper + salt)
или
hash = bcrypt(password + pepper)
(в зависимости от реализации, salt всё равно добавляется библиотекой автоматически)
Добавление pepper решает задачу защиты в сценарии утечки базы данных.
Если злоумышленник получает:
но не получает pepper, то:
Библиотека bcrypt.js не предоставляет встроенного механизма pepper, поэтому он реализуется на уровне приложения.
Обычно используется переменная окружения:
PASSWORD_PEPPER=super-secret-server-key
import bcrypt from "bcryptjs";
const PEPPER = process.env.PASSWORD_PEPPER;
async function hashPassword(password) {
const combined = password + PEPPER;
const saltRounds = 12;
return await bcrypt.hash(combined, saltRounds);
}
async function verifyPassword(password, hash) {
const combined = password + PEPPER;
return await bcrypt.compare(combined, hash);
}
Один секрет для всей системы:
password + PEPPER
Используется чаще всего благодаря простоте.
Преимущества:
Недостатки:
Разные pepper для разных окружений:
const PEPPER = process.env.PEPPER_PROD;
Это снижает риск утечки через тестовые среды.
Дополнительные варианты:
password + pepper_global + pepper_service + salt
Используется в высокозащищённых системах.
bcrypt уже обладает следующими характеристиками:
Pepper добавляет:
Если pepper утерян:
Хранение в исходниках:
const PEPPER = "hardcoded-secret";
создаёт критическую уязвимость при утечке репозитория.
// неправильно
bcrypt.hash(password + PEPPER, salt);
bcrypt.compare(password, hash); // без pepper
Результат: сравнение всегда будет ложным.
bcrypt.hash(password + PEPPER + PEPPER, salt);
Это ломает согласованность системы.
PEPPER = "123456"
В таком случае безопасность почти не увеличивается.
Смена server-side секрета — сложная операция, поскольку старые хеши становятся несовместимыми.
Типовой подход:
pepper_v1
pepper_v2
хранение версии в хеше или метаданных
проверка с fallback:
async function verify(password, hash, version) {
const pepper = getPepperByVersion(version);
return bcrypt.compare(password + pepper, hash);
}
Добавление pepper не влияет существенно на:
Поскольку операция конкатенации строки незначительна по стоимости по сравнению с вычислением bcrypt.
| Схема | Защита при утечке БД | Сложность | Риск |
|---|---|---|---|
| bcrypt + salt | средняя | низкая | утечка базы раскрывает попытки атак |
| bcrypt + pepper | высокая | средняя | потеря pepper критична |
| bcrypt + salt + pepper | очень высокая | средняя | требует защиты секретов |
Наиболее распространённая схема:
password -> pepper -> bcrypt -> hash
где:
Pepper рассматривается как часть общей модели защиты секретов:
В сочетании с bcrypt.js он усиливает стойкость системы без изменения самого алгоритма хеширования.