Алгоритм bcrypt относится к классу адаптивных функций хеширования, специально разработанных для защиты паролей. Его ключевая характеристика — высокая вычислительная стоимость, которая делает перебор паролей (brute force) крайне медленным. В библиотеке bcrypt.js, реализованной на чистом JavaScript, эта стоимость дополнительно усиливается тем, что вычисления выполняются в рамках JavaScript-движка, без нативных оптимизаций.
Параллельное хеширование нескольких паролей в таких условиях требует понимания ограничений event loop, нагрузки на CPU и особенностей асинхронной модели Node.js.
bcrypt.js выполняет вычисления синхронно внутри асинхронного API через внутреннюю имитацию неблокирующего поведения. Однако фактически каждый процесс хеширования остаётся CPU-интенсивной операцией.
При запуске множества хеширований одновременно возникают следующие эффекты:
Особенно это заметно при высокой стоимости раундов (salt rounds), например 12–15 и выше.
Самый прямолинейный способ выполнить несколько хеширований
одновременно — использовать Promise.all:
import bcrypt fr om 'bcryptjs';
const passwords = [
'password1',
'password2',
'password3',
'password4'
];
const saltRounds = 12;
const hashAll = async (list) => {
const tasks = list.map(pwd => bcrypt.hash(pwd, saltRounds));
return await Promise.all(tasks);
};
const hashes = await hashAll(passwords);
Такой подход запускает все операции одновременно, что в малых объёмах выглядит эффективно. Однако при увеличении количества паролей возникает перегрузка CPU.
Основной недостаток:
Более устойчивый подход — ограничение числа одновременно выполняемых хеширований. Это позволяет балансировать между скоростью и стабильностью системы.
Пример с ручным контролем очереди:
import bcrypt fr om 'bcryptjs';
const saltRounds = 12;
async function hashWithLimit(passwords, lim it = 2) {
const results = [];
let index = 0;
const workers = new Array(lim it).fill(null).map(async () => {
while (index < passwords.length) {
const current = index++;
const hash = await bcrypt.hash(passwords[current], saltRounds);
results[current] = hash;
}
});
await Promise.all(workers);
return results;
}
Такой подход обеспечивает:
Для production-решений часто применяется библиотека
p-limit, которая упрощает управление конкурентностью:
import bcrypt fr om 'bcryptjs';
import pLimit fr om 'p-lim it';
const lim it = pLimit(2);
const saltRounds = 12;
const passwords = ['a', 'b', 'c', 'd', 'e'];
const tasks = passwords.map(pwd =>
limit(() => bcrypt.hash(pwd, saltRounds))
);
const results = await Promise.all(tasks);
Преимущества такого подхода:
При хешировании сотен или тысяч паролей важно учитывать, что bcrypt.js не предназначен для массовых batch-операций в одном потоке без ограничений.
Типичная стратегия масштабирования включает:
Пример чанкинга:
function chunkArray(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
Далее обработка по блокам:
const chunks = chunkArray(passwords, 10);
for (const chunk of chunks) {
await Promise.all(chunk.map(p => bcrypt.hash(p, saltRounds)));
}
Такой подход снижает пиковую нагрузку и делает систему устойчивой при больших объёмах данных.
В отличие от нативной реализации bcrypt (например,
bcrypt на C++), bcrypt.js не использует потоковую модель
вычислений. Это означает:
При слишком большом числе параллельных задач возникает обратный эффект: замедление из-за конкуренции за CPU и garbage collection.
Для более тяжёлых сценариев можно вынести хеширование в отдельные потоки:
import { Worker } from 'worker_threads';
function hashInWorker(password) {
return new Promise((resolve, reject) => {
const worker = new Worker('./hash-worker.js', {
workerData: password
});
worker.on('message', resolve);
worker.on('error', reject);
});
}
Внутри worker:
import bcrypt from 'bcryptjs';
import { parentPort, workerData } from 'worker_threads';
const hash = bcrypt.hashSync(workerData, 12);
parentPort.postMessage(hash);
Такой подход:
Но требует дополнительных ресурсов и усложняет архитектуру.
Выбор метода параллельного хеширования зависит от объёма данных:
Promise.allУвеличение числа salt rounds резко повышает вычислительную сложность. При параллельной обработке это приводит к экспоненциальному росту нагрузки.
Практически важно учитывать:
Оптимальный режим работы bcrypt.js достигается не максимальным распараллеливанием, а контролируемой очередью задач с ограниченной конкуренцией.