bcrypt.js реализует криптографическую функцию хеширования паролей на основе алгоритма Blowfish с адаптивной стоимостью вычислений. Ключевая особенность заключается в параметре cost factor (salt rounds), который напрямую влияет на время вычисления хеша. При росте нагрузки на систему именно эта характеристика становится критической точкой производительности, особенно в сценариях массовой аутентификации.
Каждый вызов bcrypt.hash() требует значительных
CPU-ресурсов. Даже при умеренных значениях cost factor (10–12) операция
остаётся дорогостоящей по времени. Это сделано намеренно для защиты от
перебора паролей, но в высоконагруженных системах приводит к заметному
снижению пропускной способности.
Основные источники нагрузки:
bcrypt.compare()Особенно затратной является операция сравнения
bcrypt.compare(), так как она фактически выполняет
повторное вычисление хеша с извлечением salt из строки хеша.
В типичной архитектуре веб-приложения после успешной аутентификации
пользователь получает сессию или токен. Однако при отсутствии
оптимизации система может многократно обращаться к
bcrypt.compare() даже тогда, когда это не требуется.
Примеры неэффективного поведения:
В таких условиях bcrypt становится узким местом, даже если сама логика приложения проста.
Суть оптимизации заключается в том, чтобы минимизировать количество обращений к bcrypt после первичной проверки пароля. Сессионное кэширование позволяет сохранить результат успешной аутентификации и использовать его без повторного криптографического вычисления.
Ключевая идея:
Это переводит систему из модели «каждый запрос = проверка пароля» в модель «один раз проверили — дальше доверяем сессии».
В типичном Node.js приложении с Express и bcrypt.js схема может выглядеть следующим образом:
bcrypt.compare()Пример логики:
const bcrypt = require('bcryptjs');
async function login(req, res) {
const { password, user } = req.body;
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
return res.status(401).send('Invalid credentials');
}
req.session.userId = user.id;
req.session.authenticated = true;
req.session.authTimestamp = Date.now();
res.send('OK');
}
После этого любые запросы опираются на req.session, не
вызывая bcrypt повторно.
При горизонтальном масштабировании приложения локальные сессии становятся ограничением. В таких случаях используется внешнее хранилище, чаще всего Redis.
Redis позволяет:
Схема работы:
Пример хранения:
await redis.set(`session:${sessionId}`, JSON.stringify({
userId: user.id,
authenticated: true
}), 'EX', 3600);
В некоторых системах применяется промежуточное кэширование результата сравнения пароля. Это допустимо только в ограниченных сценариях, например при повторных попытках входа в короткий промежуток времени.
Пример логики:
login_attempt:{userId}:{hash}bcrypt.compare() сохраняется на короткое
времяОднако такая стратегия требует строгого контроля времени жизни кэша.
Оптимизация обычно строится в несколько слоёв:
Отказ от повторного bcrypt после логина
Сессионное хранилище
Distributed cache (Redis/Memcached)
Edge-кеширование (reverse proxy)
Кэширование в контексте аутентификации требует осторожности, так как ошибки приводят к прямым уязвимостям.
Основные риски:
Особенно опасно кэшировать сам факт валидности пароля на длительное время. bcrypt задуман как одноразовая проверка, а не механизм постоянной доверенной идентификации.
Сессионный кеш требует строгой стратегии инвалидирования:
Пример:
await redis.del(`session:${sessionId}`);
Также применяются короткие TTL для автоматического истечения сессий.
После успешной аутентификации нет необходимости повторно обращаться к bcrypt для проверки состояния пользователя. Вместо этого используются:
bcrypt в этом контексте остаётся исключительно механизмом первичной проверки.
Некоторые подходы приводят к избыточной нагрузке:
bcrypt.compare() на каждый middleware-запросОсобенно критично использование bcrypt.compareSync() в
обработке запросов, так как оно блокирует event loop.
В высоконагруженных системах стратегия обычно сводится к следующему:
Это позволяет стабилизировать CPU нагрузку даже при росте количества пользователей.
bcrypt создавался как намеренно медленный алгоритм. Любая оптимизация вокруг него должна учитывать, что ускорение не должно затрагивать сам процесс хеширования. Кеширование допустимо только на уровне результатов, но не на уровне криптографических операций.
В корректно спроектированной системе bcrypt остаётся ограниченным входным фильтром, а не постоянной операцией.