Pepper: добавление серверного секрета к паролю

В библиотеке bcrypt.js процесс хеширования пароля базируется на использовании встроенной соли (salt), которая делает каждый хеш уникальным даже при одинаковых входных значениях. Однако в реальных системах часто применяется дополнительный уровень защиты — pepper, серверный секрет, который не хранится в базе данных и добавляется к паролю до хеширования.


Различие между salt и pepper

Salt (соль) Соль — это случайная строка, которая генерируется для каждого пароля отдельно и хранится вместе с хешем в базе данных. Основная цель соли — защита от радужных таблиц и обеспечение уникальности хешей.

Pepper (перец) Pepper — это глобальный секрет, одинаковый для всей системы, который добавляется к паролю перед хешированием. В отличие от соли, pepper:

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

Место pepper в процессе bcrypt

Алгоритм bcrypt уже включает соль как часть своей конструкции:

hash = bcrypt(password + salt)

При использовании pepper формула изменяется:

hash = bcrypt(password + pepper + salt)

или

hash = bcrypt(password + pepper)

(в зависимости от реализации, salt всё равно добавляется библиотекой автоматически)


Причины использования pepper

Добавление pepper решает задачу защиты в сценарии утечки базы данных.

Если злоумышленник получает:

  • хеши паролей
  • соли

но не получает pepper, то:

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

Реализация pepper в bcrypt.js

Библиотека bcrypt.js не предоставляет встроенного механизма pepper, поэтому он реализуется на уровне приложения.

Хранение 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);
}

Архитектурные подходы к использованию pepper

1. Глобальный pepper

Один секрет для всей системы:

password + PEPPER

Используется чаще всего благодаря простоте.

Преимущества:

  • простая реализация
  • минимальные накладные расходы

Недостатки:

  • компрометация pepper = компрометация всей системы

2. Пер-окружение pepper

Разные pepper для разных окружений:

  • development
  • staging
  • production
const PEPPER = process.env.PEPPER_PROD;

Это снижает риск утечки через тестовые среды.


3. Многоуровневый pepper

Дополнительные варианты:

  • global pepper
  • service-level pepper
  • region-based pepper
password + pepper_global + pepper_service + salt

Используется в высокозащищённых системах.


Влияние pepper на безопасность bcrypt

bcrypt уже обладает следующими характеристиками:

  • адаптивная вычислительная сложность
  • встроенная соль
  • защита от радужных таблиц

Pepper добавляет:

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

Важные ограничения pepper

1. Невозможность восстановления паролей без pepper

Если pepper утерян:

  • все хеши становятся бесполезными
  • восстановление паролей невозможно

2. Риск хранения pepper в коде

Хранение в исходниках:

const PEPPER = "hardcoded-secret";

создаёт критическую уязвимость при утечке репозитория.


Ошибки при реализации

Ошибка 1: использование pepper только при регистрации

// неправильно
bcrypt.hash(password + PEPPER, salt);
bcrypt.compare(password, hash); // без pepper

Результат: сравнение всегда будет ложным.


Ошибка 2: двойное добавление pepper

bcrypt.hash(password + PEPPER + PEPPER, salt);

Это ломает согласованность системы.


Ошибка 3: использование слабого pepper

PEPPER = "123456"

В таком случае безопасность почти не увеличивается.


Ротация pepper

Смена server-side секрета — сложная операция, поскольку старые хеши становятся несовместимыми.

Типовой подход:

  1. ввод версии pepper
pepper_v1
pepper_v2
  1. хранение версии в хеше или метаданных

  2. проверка с fallback:

async function verify(password, hash, version) {
  const pepper = getPepperByVersion(version);
  return bcrypt.compare(password + pepper, hash);
}

Pepper и производительность

Добавление pepper не влияет существенно на:

  • скорость bcrypt
  • нагрузку CPU

Поскольку операция конкатенации строки незначительна по стоимости по сравнению с вычислением bcrypt.


Сравнение схем

Схема Защита при утечке БД Сложность Риск
bcrypt + salt средняя низкая утечка базы раскрывает попытки атак
bcrypt + pepper высокая средняя потеря pepper критична
bcrypt + salt + pepper очень высокая средняя требует защиты секретов

Практическая модель применения

Наиболее распространённая схема:

password -> pepper -> bcrypt -> hash

где:

  • salt управляется bcrypt.js автоматически
  • pepper управляется приложением
  • hash хранится в базе данных

Роль pepper в современной аутентификации

Pepper рассматривается как часть общей модели защиты секретов:

  • защита базы данных
  • защита инфраструктуры
  • разделение ответственности между слоями системы

В сочетании с bcrypt.js он усиливает стойкость системы без изменения самого алгоритма хеширования.