Асинхронное хеширование через Promise

Promise-ориентированный API в bcrypt.js позволяет выполнять хеширование и проверку паролей без использования коллбеков, опираясь на стандартный механизм Promise и синтаксис async/await. Это упрощает управление асинхронным кодом и делает цепочки операций более читаемыми, особенно в серверных приложениях Node.js.

В основе асинхронного поведения bcrypt.js лежит перегрузка функций: при отсутствии коллбека библиотека возвращает Promise, что позволяет использовать её в современных архитектурах без дополнительной обёртки.


Основные функции библиотеки поддерживают Promise-режим:

  • bcrypt.hash(data, saltRounds)
  • bcrypt.compare(data, encrypted)
  • bcrypt.genSalt(saltRounds)

Если коллбек не передаётся, результатом вызова становится Promise, который резолвится либо итоговым значением, либо ошибкой.

const bcrypt = require('bcryptjs');

bcrypt.hash('password123', 10)
  .then(hash => {
    console.log(hash);
  })
  .catch(err => {
    console.error(err);
  });

Promise-ориентированный интерфейс делает код линейным и избавляет от вложенных коллбеков, характерных для классического Node.js.


Асинхронное хеширование пароля

Хеширование пароля — одна из наиболее ресурсоёмких операций в bcrypt. В отличие от простых криптографических функций, bcrypt специально замедлен за счёт параметра cost factor (число раундов), чтобы усложнить brute-force атаки.

Использование async/await позволяет интегрировать этот процесс в поток выполнения без блокировки логики приложения:

const bcrypt = require('bcryptjs');

async function createUserPassword(password) {
  const hash = await bcrypt.hash(password, 12);
  return hash;
}

Число 12 определяет уровень сложности. Каждый дополнительный шаг увеличивает вычислительную стоимость примерно в два раза.


Соль и cost factor

При хешировании bcrypt автоматически генерирует соль, если она не была передана явно. В Promise-версии это поведение сохраняется.

const hash = await bcrypt.hash('securePassword', 10);

Внутри происходит:

  1. генерация случайной соли;
  2. применение ключевого расширения;
  3. многократное шифрование по алгоритму EksBlowfish;
  4. формирование итоговой строки формата bcrypt.

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


Проверка пароля через Promise

Сравнение введённого пароля с хешем также реализовано в асинхронном виде:

const bcrypt = require('bcryptjs');

async function verifyPassword(password, hash) {
  const match = await bcrypt.compare(password, hash);
  return match;
}

Функция compare автоматически извлекает соль из хеша и выполняет повторное вычисление, после чего сравнивает результаты в безопасном режиме, предотвращающем timing-атаки.


Обработка ошибок в асинхронном режиме

При использовании async/await ошибки становятся обычными исключениями, что упрощает контроль потока выполнения:

async function safeHash(password) {
  try {
    const hash = await bcrypt.hash(password, 10);
    return hash;
  } catch (err) {
    throw new Error('Ошибка хеширования');
  }
}

В Promise-цепочках аналогичная логика реализуется через catch.


Особенности асинхронности в bcrypt.js

bcrypt.js написан на чистом JavaScript и не использует нативные биндинги C/C++. Это влияет на характер асинхронности:

  • вычисления выполняются в основном потоке JavaScript;
  • асинхронность достигается через разбиение работы на этапы;
  • event loop не блокируется полностью, но нагрузка на CPU сохраняется.

Это отличает bcrypt.js от реализации bcrypt, которая использует нативные расширения и может распределять нагрузку более эффективно через libuv thread pool.


Сценарии использования Promise API

Promise-интерфейс особенно полезен в следующих случаях:

  • API-сервисы на Express или Fastify;
  • регистрация и авторизация пользователей;
  • обработка паролей в микросервисной архитектуре;
  • интеграция с ORM, поддерживающими async/await.

Пример в HTTP-обработчике:

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

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

  await saveUser({ password: hash });

  res.sendStatus(201);
});

Цепочки Promise и комбинирование операций

bcrypt.js хорошо сочетается с другими асинхронными операциями:

bcrypt.hash(password, 10)
  .then(hash => database.save(hash))
  .then(() => sendResponse())
  .catch(err => handleError(err));

Такой подход позволяет выстраивать линейные цепочки обработки данных без вложенных структур.


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

При выборе параметра cost factor важно учитывать баланс между безопасностью и производительностью:

  • низкие значения (8–10) подходят для высоконагруженных систем;
  • средние (10–12) — стандарт для большинства веб-приложений;
  • высокие (14+) существенно увеличивают время вычислений.

Асинхронный интерфейс не ускоряет само хеширование, но предотвращает блокировку основного потока, что критично для серверов с большим количеством одновременных запросов.


Сравнение async/await и Promise chaining

Оба подхода эквивалентны по функциональности, но отличаются читаемостью:

async/await:

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

Promise:

bcrypt.hash(password, 10).then(hash => hash);

В сложных сценариях с несколькими последовательными операциями async/await обеспечивает более предсказуемую структуру кода и упрощает обработку ошибок через try/catch.