Передача готовой соли в функцию хеширования

В bcrypt соль представляет собой не просто случайный набор байтов, а строго структурированную строку, содержащую метаданные алгоритма и параметры сложности. Она кодируется в виде строки формата:

$2b$12$C6UzMDM.H6dfI/f/IKcEeO

Структура этой строки:

  • $2b$ — версия алгоритма bcrypt
  • 12$ — cost factor (число раундов, влияющее на вычислительную сложность)
  • оставшаяся часть — 128-битная соль, закодированная в Base64-подобном формате bcrypt

Именно эта строка целиком и передаётся в функцию хеширования как готовая соль.

Ключевой момент: bcrypt.js не требует разбирать соль вручную. Она воспринимается как атомарный объект конфигурации алгоритма.


Передача готовой соли в функцию хеширования

В bcrypt.js функция hash имеет перегруженную сигнатуру. Один из поддерживаемых вариантов — передача уже сгенерированной соли:

bcrypt.hash(password, salt)

где:

  • password — исходная строка пароля
  • salt — заранее подготовленная строка соли в формате bcrypt

Пример:

import bcrypt from "bcryptjs";

const salt = "$2b$10$KIXQ9m3Y7V5Yz1Q8ePz0eu"; // заранее сохранённая соль

const password = "mySecretPassword";

const hash = bcrypt.hashSync(password, salt);

console.log(hash);

В этом случае библиотека не генерирует новую соль, а использует предоставленную. Это означает, что итоговый хеш будет полностью детерминированным при одинаковых входных данных.


Отличие от генерации соли внутри bcrypt

Обычно используется другой сценарий:

const salt = bcrypt.genSaltSync(10);
const hash = bcrypt.hashSync(password, salt);

или более короткая форма:

const hash = bcrypt.hashSync(password, 10);

Во втором случае библиотека сама:

  • генерирует случайную соль
  • встраивает cost factor
  • формирует корректную строку соли

Передача готовой соли отключает этот этап и переносит контроль над параметрами на разработчика.


Практическое значение передачи соли

Использование заранее подготовленной соли встречается значительно реже, чем автоматическая генерация, но имеет специфические применения:

1. Воспроизводимость хешей

Если используется фиксированная соль, один и тот же пароль всегда даёт одинаковый хеш:

const salt = "$2b$10$aaaaaaaaaaaaaaaaaaaaaa";

bcrypt.hashSync("password123", salt);
bcrypt.hashSync("password123", salt); // идентичный результат

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


2. Миграция данных из старых систем

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


3. Контроль параметров безопасности

Переданная соль фиксирует cost factor. Например:

  • $2b$08$... — быстрее, но слабее
  • $2b$12$... — медленнее, но устойчивее

Таким образом можно унифицировать поведение системы без зависимости от genSalt.


Внутреннее поведение bcrypt.js при передаче соли

При вызове:

bcrypt.hash(password, salt)

библиотека выполняет следующие шаги:

  1. Проверяет формат строки соли
  2. Извлекает cost factor
  3. Декодирует соль из строкового представления
  4. Передаёт параметры в криптографическое ядро
  5. Выполняет итеративное шифрование EksBlowfish
  6. Формирует итоговый хеш, включая соль в результат

Важно: итоговый хеш всегда содержит соль внутри себя, даже если она была передана извне.


Типичные ошибки при передаче соли

Использование неполной строки

Нельзя передавать только часть соли:

bcrypt.hashSync(password, "10"); // ошибка логики

bcrypt.js ожидает полную строку формата $2b$....


Повторное использование одной соли для разных пользователей

Если одна и та же соль применяется ко многим паролям, исчезает уникальность хешей:

const salt = "$2b$10$fixedSalt.............";

bcrypt.hashSync("user1pass", salt);
bcrypt.hashSync("user2pass", salt);

Это снижает устойчивость к атаке через предварительно вычисленные таблицы (rainbow tables).


Хранение соли отдельно от хеша без необходимости

bcrypt уже включает соль внутрь хеша. Разделение этих данных:

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

Поведение при асинхронном API

Аналогичная логика действует и в асинхронной версии:

bcrypt.hash(password, salt, (err, hash) => {
  console.log(hash);
});

или через Promise:

const hash = await bcrypt.hash(password, salt);

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


Формирование соли вручную

Технически соль можно сформировать через genSalt, а затем повторно использовать:

const salt = await bcrypt.genSalt(12);
const hash = await bcrypt.hash(password, salt);

Или сохранить её для повторного применения:

const storedSalt = await bcrypt.genSalt(12);

// позже
const hash = await bcrypt.hash(password, storedSalt);

Однако в большинстве сценариев хранения пользователей такой подход не применяется, поскольку bcrypt уже инкапсулирует соль внутри результата хеширования.


Влияние соли на результат хеширования

При одинаковом пароле:

  • разная соль → разный хеш
  • одинаковая соль → одинаковый хеш

Пример:

const salt1 = await bcrypt.genSalt(10);
const salt2 = await bcrypt.genSalt(10);

bcrypt.hashSync("password", salt1);
bcrypt.hashSync("password", salt2);

Результаты будут различаться из-за различий в случайной части соли.


Сравнение подходов использования соли

Подход Генерация соли Поведение
hash(password, 10) автоматическая безопасный стандарт
hash(password, salt) ручная полный контроль
повторное использование соли фиксированная детерминированность

Роль соли внутри bcrypt-алгоритма

Соль в bcrypt выполняет сразу несколько функций:

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

Передача готовой соли не меняет криптографическую модель алгоритма, а лишь фиксирует входные параметры функции EksBlowfish.