Библиотека bcrypt.js изначально проектировалась с
поддержкой асинхронного API на основе callback-функций. При
использовании современного JavaScript-стека предпочтение отдается
async/await, что требует оборачивания callback-методов в
Promise-слой.
Основные функции библиотеки:
bcrypt.hash(password, saltRounds, callback)bcrypt.compare(password, hash, callback)bcrypt.genSalt(saltRounds, callback)Нативно Promise-версии отсутствуют, поэтому интеграция с
async/await выполняется через util.promisify
или ручную обертку.
Стандартный способ адаптации библиотеки под async/await —
использование util.promisify.
const bcrypt = require('bcryptjs');
const { promisify } = require('util');
const hashAsync = promisify(bcrypt.hash);
const compareAsync = promisify(bcrypt.compare);
const genSaltAsync = promisify(bcrypt.genSalt);
После преобразования функции становятся совместимыми с
await:
const salt = await genSaltAsync(10);
const hash = await hashAsync('password123', salt);
const isValid = await compareAsync('password123', hash);
Такой подход сохраняет производительность неблокирующих операций и позволяет использовать линейную структуру кода.
Хеширование пароля в bcrypt основано на вычислительно дорогой функции, что делает синхронные версии нежелательными в серверной среде.
Пример асинхронного хеширования:
const bcrypt = require('bcryptjs');
const { promisify } = require('util');
const hashAsync = promisify(bcrypt.hash);
const genSaltAsync = promisify(bcrypt.genSalt);
async function createPasswordHash(password) {
const saltRounds = 12;
const salt = await genSaltAsync(saltRounds);
const hash = await hashAsync(password, salt);
return hash;
}
Количество saltRounds определяет вычислительную
сложность. Значения 10–12 считаются балансом между безопасностью и
производительностью.
Сравнение пароля с хешем также выполняется асинхронно:
const bcrypt = require('bcryptjs');
const { promisify } = require('util');
const compareAsync = promisify(bcrypt.compare);
async function verifyPassword(password, hash) {
const match = await compareAsync(password, hash);
return match;
}
Внутри bcrypt.compare происходит извлечение соли из хеша
и повторное вычисление значения, что делает процесс устойчивым к утечкам
исходного значения.
Асинхронная модель требует обработки ошибок через
try/catch, так как любые сбои в вычислении хеша или
сравнения приводят к исключениям Promise.
async function safeHash(password) {
try {
const salt = await genSaltAsync(10);
return await hashAsync(password, salt);
} catch (err) {
throw new Error('Ошибка при генерации хеша');
}
}
Типовые причины ошибок:
При обработке больших массивов пользователей важно учитывать конкурентное выполнение промисов.
async function hashUsers(users) {
return await Promise.all(
users.map(async (user) => {
const hash = await hashAsync(user.password, 10);
return {
id: user.id,
hash
};
})
);
}
Promise.all позволяет выполнять операции параллельно,
однако при высокой нагрузке возможна перегрузка event loop, поэтому в
production-системах часто применяется ограничение concurrency.
Для снижения нагрузки используется последовательная обработка:
async function hashUsersSequential(users) {
const result = [];
for (const user of users) {
const hash = await hashAsync(user.password, 10);
result.push({
id: user.id,
hash
});
}
return result;
}
Такой подход снижает пиковую нагрузку, но увеличивает общее время выполнения.
В некоторых проектах создается собственная Promise-обертка:
function hash(password, saltRounds) {
return new Promise((resolve, reject) => {
bcrypt.hash(password, saltRounds, (err, hash) => {
if (err) reject(err);
else resolve(hash);
});
});
}
function compare(password, hash) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, hash, (err, res) => {
if (err) reject(err);
else resolve(res);
});
});
}
Такой вариант дает полный контроль над интерфейсом API и часто используется в архитектурах с единым стилем Promise.
bcrypt выполняет CPU-интенсивные операции, которые выносятся из основного потока через внутренние механизмы thread pool (в случае Node.js). Это позволяет сохранять отзывчивость приложения даже при активном хешировании.
При увеличении saltRounds растет нагрузка на процессор,
что напрямую влияет на latency запросов.
Ключевые практики оптимизации:
saltRounds в рамках
окруженияbcrypt.hashSync и
bcrypt.compareSyncconst { promisify } = require('util');
const bcrypt = require('bcryptjs');
const hashAsync = promisify(bcrypt.hash);
const compareAsync = promisify(bcrypt.compare);
class AuthService {
async register(password) {
const hash = await hashAsync(password, 12);
return hash;
}
async login(password, hash) {
return await compareAsync(password, hash);
}
}
Такая структура отделяет криптографическую логику от бизнес-слоя и упрощает тестирование.
При интенсивном использовании bcrypt.js наблюдаются следующие эффекты:
saltRoundsАрхитектурно часто применяется вынесение хеширования в отдельные worker-процессы или микросервисы.