Криптографические операции в JavaScript часто оказываются вычислительно тяжёлыми. Даже такие базовые алгоритмы, как SHA-256, HMAC или PBKDF2, при обработке больших объёмов данных могут блокировать основной поток выполнения браузера. Это приводит к «замораживанию» интерфейса: перестают реагировать кнопки, анимации и ввод пользователя. Причина проста — JavaScript в браузере по умолчанию однопоточен, и любые долгие вычисления выполняются синхронно.
Библиотека CryptoJS реализована на чистом JavaScript и не использует нативные расширения. Это делает её универсальной, но увеличивает нагрузку на основной поток при интенсивных вычислениях.
Типичные сценарии, где возникает проблема производительности:
T(n) = O(n k)
где n — размер данных, а k — число итераций алгоритма (например, PBKDF2). При росте любого из параметров время выполнения становится критичным для UI-потока.
В браузере существует ограничение: один поток отвечает за DOM, события и выполнение JS. Если криптографическая функция занимает даже 200–300 мс, интерфейс уже ощущается «дерганым». Поэтому оптимальная архитектура требует выноса вычислений в Web Workers.
Web Workers предоставляют механизм фонового выполнения
JavaScript-кода в отдельном потоке. Они не имеют доступа к DOM, но могут
обмениваться сообщениями с основным потоком через
postMessage.
Основные характеристики:
Архитектурно это выглядит так:
Минимальная схема использования выглядит следующим образом.
importScripts('crypto-js.js');
self.onmess age = function (e) {
const { type, payload } = e.data;
if (type === 'SHA256') {
const hash = CryptoJS.SHA256(payload).toString();
self.postMessage({ type: 'RESULT', hash });
}
if (type === 'HMAC') {
const { message, key } = payload;
const result = CryptoJS.HmacSHA256(message, key).toString();
self.postMessage({ type: 'RESULT', result });
}
};
const worker = new Worker('crypto-worker.js');
worker.onmess age = function (e) {
console.log('Результат:', e.data);
};
worker.postMessage({
type: 'SHA256',
payload: 'Hello World'
});
При проектировании системы важно учитывать не только перенос вычислений, но и структуру задач.
Крупные операции следует дробить:
Это позволяет распределять нагрузку между несколькими worker-ами.
Один worker — ограничение по параллелизму. Для реальной нагрузки применяется пул:
Пример простой реализации пула:
class WorkerPool {
constructor(size, script) {
this.workers = Array.from({ length: size }, () => new Worker(script));
this.queue = [];
this.index = 0;
}
exec(task) {
return new Promise((resolve) => {
const worker = this.workers[this.index];
this.index = (this.index + 1) % this.workers.length;
worker.onmess age = (e) => resolve(e.data);
worker.postMessage(task);
});
}
}
При работе с CryptoJS важно учитывать, что библиотека не потоковая по своей природе. Она ожидает входные данные целиком. Поэтому при работе с большими файлами используется стратегия чанков.
importScripts('crypto-js.js');
let sha = CryptoJS.algo.SHA256.create();
self.onmess age = function (e) {
const { chunk, isFinal } = e.data;
sha.update(CryptoJS.enc.Utf8.parse(chunk));
if (isFinal) {
const hash = sha.finalize().toString();
self.postMessage({ hash });
sha = CryptoJS.algo.SHA256.create();
}
};
Такой подход позволяет:
PBKDF2 — одна из самых дорогих по вычислениям функций.
T = c h(n)
где:
При значениях итераций 100000+ выполнение в UI-потоке недопустимо.
Worker-реализация:
importScripts('crypto-js.js');
self.onmess age = function (e) {
const { password, salt, iterations } = e.data;
const key = CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations
});
self.postMessage({ key: key.toString() });
};
Web Workers используют структурированное клонирование. Это означает:
ArrayBuffer.Оптимизация:
worker.postMessage(buffer, [buffer]);
Это позволяет использовать transferables без копирования памяти.
Несмотря на вынесение в отдельный поток, остаются ограничения:
CryptoJS полностью интерпретируемый, поэтому уступает WebCrypto API.
Передача больших строк или объектов может стать bottleneck.
При частых вызовах возможны паузы GC.
Web Crypto API выполняется нативно и часто быстрее:
Однако Web Crypto:
Поэтому CryptoJS в Web Workers используется там, где нужна гибкость.
Часто строится абстракция уровня сервиса:
Пример интерфейса:
cryptoService.sha256(data)
cryptoService.hmac(message, key)
cryptoService.pbkdf2(password, salt, iterations)
Внутри — очередь задач и worker pool.
Web Workers можно завершать принудительно:
worker.terminate();
Но это грубый способ. Более корректный подход:
При проектировании криптографического слоя с CryptoJS и Web Workers важно учитывать:
UI Layer
↓
Task Queue
↓
Worker Pool
↓
CryptoJS Engine (inside workers)
↓
Results Aggregation
Такой подход обеспечивает стабильную работу интерфейса даже при высокой криптографической нагрузке и позволяет масштабировать вычисления без переписывания бизнес-логики.