Криптографические операции в браузере выполняются через Web Crypto
API (crypto.subtle) и относятся к числу наиболее
ресурсоёмких задач: генерация ключей, шифрование больших объёмов данных,
вычисление хэшей и подпись сообщений могут существенно нагружать
основной поток выполнения JavaScript. Любая блокировка main thread
напрямую влияет на отзывчивость интерфейса, задержки обработки событий и
плавность UI.
Перенос криптографических вычислений в Web Workers позволяет изолировать тяжёлую работу от интерфейсного потока и использовать многопоточность браузера без доступа к DOM.
Web Worker работает в отдельном контексте выполнения JavaScript, который:
Web Crypto API при этом доступен внутри worker-контекста, если выполняются условия безопасного контекста (HTTPS или localhost).
Ключевая модель выглядит следующим образом:
crypto.subtlepostMessageВнутри worker доступен тот же интерфейс:
crypto.subtle.generateKeycrypto.subtle.encryptcrypto.subtle.decryptcrypto.subtle.signcrypto.subtle.verifycrypto.subtle.digestcrypto.subtle.importKeycrypto.subtle.exportKeyОсобенность: объект crypto в worker не зависит от window
и доступен как глобальный.
Передача данных между main thread и worker осуществляется через structured clone algorithm.
Поддерживаются:
Для оптимизации используется transfer ownership:
worker.postMessage(buffer, [buffer]);
После передачи buffer становится “detached” в исходном потоке, что уменьшает копирование и повышает производительность при работе с большими данными (например, файлами для шифрования).
Worker-файл:
self.onmess age = async (event) => {
const { type, data } = event.data;
if (type === "hash") {
const hash = await crypto.subtle.digest("SHA-256", data);
self.postMessage({ type: "hashResult", hash }, [hash]);
}
};
Главный поток:
const worker = new Worker("crypto-worker.js");
const data = new TextEncoder().encode("example data");
worker.postMessage({
type: "hash",
data
}, [data]);
worker.onmess age = (event) => {
const { hash } = event.data;
console.log(new Uint8Array(hash));
};
Операции хэширования особенно выгодно выносить в worker, так как они линейно зависят от размера входных данных.
Пример:
self.onmess age = async ({ data }) => {
const hashBuffer = await crypto.subtle.digest("SHA-256", data.chunk);
self.postMessage({ index: data.index, hash: hashBuffer });
};
Потоковая обработка:
Генерация ключей RSA или ECDSA может занимать значительное время.
Worker:
self.onmess age = async () => {
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
self.postMessage({ keyPair });
};
Важно учитывать:
AES-GCM часто используется для клиентского шифрования.
Worker:
self.onmess age = async ({ data }) => {
const { key, iv, plaintext } = data;
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
plaintext
);
self.postMessage({ ciphertext }, [ciphertext]);
};
Особенности:
Для обмена ключами между потоками и сохранения:
const exported = await crypto.subtle.exportKey("raw", key);
В worker:
const key = await crypto.subtle.importKey(
"raw",
rawKey,
"AES-GCM",
false,
["encrypt", "decrypt"]
);
Worker не имеет доступа к:
Все взаимодействие только через сообщения.
Не все объекты можно передать:
Передача больших ArrayBuffer без transfer list приводит к копированию.
Оптимальная практика:
worker.postMessage(data, [data.buffer]);
Для больших файлов используется chunk-based pipeline:
Worker может обрабатывать несколько сообщений параллельно, но порядок не гарантируется без индексации.
Несколько worker-ов могут использоваться для распараллеливания:
Пример стратегии:
Использование Web Crypto API внутри worker сохраняет свойства безопасности:
Однако архитектурные риски остаются:
В классических worker допустимо:
importScripts("utils.js");
Однако при работе с Web Crypto API это редко требуется, так как API нативный и не требует полифиллов.
Без worker:
С worker:
Часто используется следующая структура:
Такая архитектура характерна для: