В браузерных криптографических приложениях вычислительная нагрузка часто становится узким местом, особенно при работе с асимметричными алгоритмами, генерацией ключей и массовым шифрованием данных. TweetNaCl.js и nacl.js реализуют криптографию на чистом JavaScript и WebAssembly-оптимизациях не используют, поэтому все операции выполняются в основном потоке, что может приводить к заметным задержкам интерфейса.
Web Workers предоставляют изолированную среду исполнения, в которой JavaScript выполняется параллельно с основным потоком. В контексте криптографии это решает сразу несколько задач:
TweetNaCl.js хорошо подходит для работы в воркерах, поскольку не зависит от браузерного DOM и не требует глобальных объектов окна.
Связь между потоками строится через механизм сообщений
postMessage. Основной поток отправляет задачу, воркер
выполняет криптографическую операцию и возвращает результат.
Типичная схема включает:
Внутренний протокол взаимодействия обычно строится вокруг типов операций:
Воркер-скрипт инициализируется как отдельный модуль:
// worker.js
importScripts('nacl.min.js');
self.onmess age = function (event) {
const { id, type, payload } = event.data;
try {
let result;
switch (type) {
case 'generateKeyPair':
result = nacl.box.keyPair();
break;
case 'sign':
result = nacl.sign.detached(payload.message, payload.secretKey);
break;
case 'verify':
result = nacl.sign.detached.verify(
payload.message,
payload.signature,
payload.publicKey
);
break;
case 'encrypt':
result = nacl.box(
payload.message,
payload.nonce,
payload.publicKey,
payload.secretKey
);
break;
case 'decrypt':
result = nacl.box.open(
payload.ciphertext,
payload.nonce,
payload.publicKey,
payload.secretKey
);
break;
default:
throw new Error('Unknown operation');
}
self.postMessage({ id, result });
} catch (e) {
self.postMessage({ id, error: e.message });
}
};
Основной поток работает с воркером через идентификаторы задач:
const worker = new Worker('worker.js');
function request(type, payload) {
const id = crypto.randomUUID();
return new Promise((resolve, reject) => {
worker.postMessage({ id, type, payload });
worker.onmess age = (event) => {
if (event.data.id !== id) return;
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data.result);
}
};
});
}
Криптографические операции TweetNaCl.js работают с
Uint8Array. Для минимизации накладных расходов используется
передача буферов через механизм transferables.
Передача без копирования:
worker.postMessage(
{
id,
type: 'encrypt',
payload: {
message: messageBuffer,
nonce,
publicKey,
secretKey
}
},
[messageBuffer.buffer]
);
Использование transferables исключает глубокое копирование данных и существенно ускоряет обработку больших сообщений.
Операция генерации ключей является одной из самых затратных в криптографии на основе эллиптических кривых.
case 'generateKeyPair':
const keyPair = nacl.box.keyPair();
result = {
publicKey: keyPair.publicKey,
secretKey: keyPair.secretKey
};
break;
Результаты возвращаются в виде Uint8Array, которые затем
могут быть сохранены или закэшированы.
Подпись сообщений выполняется через nacl.sign.detached,
что позволяет отделять подпись от данных.
case 'sign':
result = nacl.sign.detached(payload.message, payload.secretKey);
break;
Проверка выполняется без раскрытия приватного ключа:
case 'verify':
result = nacl.sign.detached.verify(
payload.message,
payload.signature,
payload.publicKey
);
break;
Операции хорошо параллелятся и масштабируются на несколько воркеров.
Symmetric-asymmetric гибридный подход в TweetNaCl.js часто
реализуется через box.
case 'encrypt':
result = nacl.box(
payload.message,
payload.nonce,
payload.publicKey,
payload.secretKey
);
break;
case 'decrypt':
result = nacl.box.open(
payload.ciphertext,
payload.nonce,
payload.publicKey,
payload.secretKey
);
break;
Nonce должен быть уникальным для каждой операции, иначе нарушается криптографическая стойкость.
При интенсивных нагрузках один воркер становится недостаточным. Используется пул воркеров:
Простейшая модель распределения:
class WorkerPool {
constructor(size) {
this.workers = Array.from(
{ length: size },
() => new Worker('worker.js')
);
this.queue = [];
this.index = 0;
}
exec(type, payload) {
const worker = this.workers[this.index];
this.index = (this.index + 1) % this.workers.length;
return new Promise((resolve) => {
const id = crypto.randomUUID();
worker.onmess age = (event) => {
if (event.data.id === id) {
resolve(event.data.result);
}
};
worker.postMessage({ id, type, payload });
});
}
}
Web Workers не поддерживают отмену вычислений напрямую. Используются следующие подходы:
if (event.data.cancelled) return;
При критических криптографических операциях предпочтительнее пересоздание воркера.
Основные ограничения производительности:
Оптимизации:
TweetNaCl.js изначально спроектирован как автономная библиотека без внешних зависимостей, что упрощает интеграцию в Web Worker:
windowОднако важно учитывать:
importScripts или
bundlerПри использовании Webpack, Vite или Rollup воркеры подключаются как отдельные чанки:
const worker = new Worker(
new URL('./worker.js', import.meta.url),
{ type: 'module' }
);
TweetNaCl.js может быть включён:
Выбор формата влияет на размер итогового бандла и время инициализации воркера.
Использование Web Workers накладывает ряд архитектурных ограничений:
При этом криптографическая нагрузка хорошо ложится на модель изолированных потоков, особенно при обработке больших объёмов данных или параллельных операций подписи и шифрования.