Асинхронное хеширование с callback: bcrypt.hash

bcrypt.hash в bcrypt.js — основной способ создания криптографически стойкого хеша пароля в асинхронном режиме с использованием callback-функции. Асинхронный вариант критически важен для Node.js-приложений, так как позволяет не блокировать event loop при вычислении дорогостоящего хеша.


Сигнатура функции

bcrypt.hash(data, saltOrRounds, callback)

Где:

  • data — исходная строка (обычно пароль)
  • saltOrRounds — либо готовая соль, либо число раундов генерации соли
  • callback — функция обратного вызова

Callback имеет стандартную форму:

function(err, hash)
  • err — ошибка (если произошла)
  • hash — итоговый bcrypt-хеш

Базовое использование

Наиболее распространённый сценарий — хеширование пароля пользователя при регистрации.

const bcrypt = require('bcryptjs');

const password = 'user_password_123';

bcrypt.hash(password, 10, (err, hash) => {
  if (err) {
    console.error('Ошибка хеширования:', err);
    return;
  }

  console.log('Хеш пароля:', hash);
});

Число 10 обозначает количество раундов соль-генерации (cost factor). Чем выше значение, тем медленнее вычисление и выше устойчивость к перебору.


Роль salt rounds

В bcrypt соль не просто случайная строка — она генерируется на основе количества раундов:

bcrypt.hash(password, 12, callback)
  • 8–10 — быстро, но менее устойчиво
  • 11–12 — баланс безопасности и производительности
  • 13+ — высокая криптостойкость, но значительная нагрузка на CPU

Каждое увеличение на 1 примерно удваивает время вычисления хеша.


Обработка ошибок в callback

Асинхронный стиль требует обязательной обработки ошибки первым параметром:

bcrypt.hash(password, 10, (err, hash) => {
  if (err) {
    // обработка ошибок (например, логирование)
    return;
  }

  // безопасное использование hash
});

Типичные причины ошибок:

  • неверный тип входных данных (null, undefined)
  • некорректное значение cost factor
  • внутренние ошибки библиотеки

Генерация соли отдельно

Иногда требуется явное управление солью:

bcrypt.genSalt(12, (err, salt) => {
  if (err) return;

  bcrypt.hash(password, salt, (err, hash) => {
    if (err) return;

    console.log(hash);
  });
});

Такой подход полезен, когда:

  • требуется переиспользовать соль
  • необходима более тонкая настройка процесса хеширования
  • выполняется интеграция с legacy-системами

Особенности асинхронного выполнения

bcrypt.hash выполняется вне основного потока JavaScript, используя внутренние механизмы C++ binding (через bcrypt-реализацию). Это означает:

  • event loop не блокируется
  • CPU-нагрузка изолируется
  • возможна параллельная обработка запросов

Однако при высокой нагрузке:

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

Пример в реальной регистрации пользователя

const bcrypt = require('bcryptjs');

function registerUser(email, password, callback) {
  bcrypt.hash(password, 10, (err, hash) => {
    if (err) return callback(err);

    const user = {
      email,
      passwordHash: hash
    };

    // имитация сохранения в БД
    callback(null, user);
  });
}

Частые ошибки при использовании callback-версии

Игнорирование err

bcrypt.hash(password, 10, (err, hash) => {
  console.log(hash); // опасно: err не проверяется
});

Слишком низкий cost factor

bcrypt.hash(password, 4, callback);

Такое значение делает хеш уязвимым к перебору.

Блокировка логики внутри callback

Длинные синхронные операции внутри callback могут нивелировать преимущество асинхронности.


Производительность и нагрузка

bcrypt намеренно медленный алгоритм. Его цель — защита от brute-force атак.

Факторы влияния:

  • cost factor (главный параметр)
  • аппаратные ресурсы CPU
  • количество параллельных запросов

При масштабировании систем обычно применяются:

  • очередь задач (job queue)
  • ограничение числа одновременных хеширований
  • отдельные worker-процессы

Поведение при высоком параллелизме

При большом количестве вызовов:

bcrypt.hash(password, 12, callback);

возможны:

  • рост latency API
  • увеличение очередей в event loop
  • CPU saturation

Поэтому bcrypt часто выносится в отдельные воркеры или микросервисы.


Типизация входных данных

bcrypt.hash ожидает:

  • string или Buffer в качестве пароля

Некорректные значения:

bcrypt.hash(null, 10, callback); // ошибка
bcrypt.hash(undefined, 10, callback); // ошибка

Перед вызовом обычно выполняется валидация:

if (typeof password !== 'string' || password.length === 0) {
  return callback(new Error('Invalid password'));
}

Поведение при одинаковых паролях

Каждый вызов bcrypt.hash генерирует уникальную соль автоматически (если передано число rounds), поэтому:

bcrypt.hash('password', 10, ...)
bcrypt.hash('password', 10, ...)

дают разные результаты, что обеспечивает:

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

Интеграция с Express (типовой паттерн)

app.post('/register', (req, res) => {
  const { password } = req.body;

  bcrypt.hash(password, 10, (err, hash) => {
    if (err) {
      return res.status(500).send('Ошибка сервера');
    }

    // сохранение hash в БД
    res.status(201).json({ success: true });
  });
});

Особенности использования callback-подхода

  • полностью совместим со старым Node.js-кодом
  • не требует Promise/async-await
  • удобен для простых потоков обработки

При этом в сложных цепочках callback-версия приводит к:

  • вложенности кода (callback hell)
  • усложнению обработки ошибок
  • трудностям композиции логики

Поведение при повторных вызовах

Каждый вызов bcrypt.hash полностью независим:

  • не кэшируется
  • не переиспользует результат
  • всегда вычисляется заново

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


Ключевые аспекты безопасности

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