Web Workers для криптографических операций

Криптографические операции в браузере выполняются через Web Crypto API (crypto.subtle) и относятся к числу наиболее ресурсоёмких задач: генерация ключей, шифрование больших объёмов данных, вычисление хэшей и подпись сообщений могут существенно нагружать основной поток выполнения JavaScript. Любая блокировка main thread напрямую влияет на отзывчивость интерфейса, задержки обработки событий и плавность UI.

Перенос криптографических вычислений в Web Workers позволяет изолировать тяжёлую работу от интерфейсного потока и использовать многопоточность браузера без доступа к DOM.


Архитектура выполнения криптографии вне main thread

Web Worker работает в отдельном контексте выполнения JavaScript, который:

  • не имеет доступа к DOM
  • не может напрямую взаимодействовать с UI
  • использует message passing (postMessage)
  • имеет собственный event loop

Web Crypto API при этом доступен внутри worker-контекста, если выполняются условия безопасного контекста (HTTPS или localhost).

Ключевая модель выглядит следующим образом:

  • главный поток отправляет данные в worker
  • worker выполняет криптографическую операцию через crypto.subtle
  • результат возвращается обратно через postMessage

Доступность Web Crypto API в Worker

Внутри worker доступен тот же интерфейс:

  • crypto.subtle.generateKey
  • crypto.subtle.encrypt
  • crypto.subtle.decrypt
  • crypto.subtle.sign
  • crypto.subtle.verify
  • crypto.subtle.digest
  • crypto.subtle.importKey
  • crypto.subtle.exportKey

Особенность: объект crypto в worker не зависит от window и доступен как глобальный.


Передача данных между потоками

Передача данных между main thread и worker осуществляется через structured clone algorithm.

Поддерживаются:

  • ArrayBuffer
  • TypedArray
  • Object (без функций)
  • CryptoKey (ограниченно)

Для оптимизации используется transfer ownership:

worker.postMessage(buffer, [buffer]);

После передачи buffer становится “detached” в исходном потоке, что уменьшает копирование и повышает производительность при работе с большими данными (например, файлами для шифрования).


Базовая структура worker для криптографии

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

Операции хэширования особенно выгодно выносить в worker, так как они линейно зависят от размера входных данных.

Пример:

self.onmess age = async ({ data }) => {
  const hashBuffer = await crypto.subtle.digest("SHA-256", data.chunk);
  self.postMessage({ index: data.index, hash: hashBuffer });
};

Потоковая обработка:

  • данные разбиваются на чанки
  • каждый чанк отправляется в worker
  • результаты агрегируются

Генерация ключей в отдельном потоке

Генерация ключей 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 });
};

Важно учитывать:

  • CryptoKey можно передавать между потоками
  • ключ должен быть extractable: true, если требуется экспорт

Шифрование и дешифрование в worker

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]);
};

Особенности:

  • CryptoKey передаётся через structured clone
  • IV должен быть уникальным
  • данные должны быть ArrayBuffer

Импорт и экспорт ключей

Для обмена ключами между потоками и сохранения:

const exported = await crypto.subtle.exportKey("raw", key);

В worker:

const key = await crypto.subtle.importKey(
  "raw",
  rawKey,
  "AES-GCM",
  false,
  ["encrypt", "decrypt"]
);

Ограничения и нюансы

Отсутствие DOM

Worker не имеет доступа к:

  • document
  • window
  • localStorage

Все взаимодействие только через сообщения.


Structured Clone ограничения

Не все объекты можно передать:

  • функции нельзя
  • DOM-узлы нельзя
  • некоторые Proxy-объекты не поддерживаются

CryptoKey особенности

  • передаётся, но с ограничениями
  • зависит от extractable
  • нельзя сериализовать вручную

Производительность передачи данных

Передача больших ArrayBuffer без transfer list приводит к копированию.

Оптимальная практика:

worker.postMessage(data, [data.buffer]);

Потоковая модель шифрования

Для больших файлов используется chunk-based pipeline:

  1. чтение данных (File API)
  2. разбиение на блоки
  3. отправка в worker
  4. параллельное шифрование
  5. сбор результата

Worker может обрабатывать несколько сообщений параллельно, но порядок не гарантируется без индексации.


Параллелизм и масштабирование

Несколько worker-ов могут использоваться для распараллеливания:

  • hashing pipeline
  • encryption pipeline
  • signature verification batch

Пример стратегии:

  • 1 worker = 1 CPU core
  • распределение чанков round-robin
  • агрегация результатов в main thread

Безопасность криптографии в worker

Использование Web Crypto API внутри worker сохраняет свойства безопасности:

  • операции выполняются в изолированном контексте
  • ключи не покидают sandbox браузера
  • доступ к нативной реализации ОС

Однако архитектурные риски остаются:

  • утечка ключей через structured clone
  • логирование данных в main thread
  • хранение ключей в памяти без очистки

Использование importScripts в криптографических worker

В классических worker допустимо:

importScripts("utils.js");

Однако при работе с Web Crypto API это редко требуется, так как API нативный и не требует полифиллов.


Сравнение выполнения: main thread vs worker

Без worker:

  • блокировка UI при SHA-256 от больших файлов
  • задержки event loop
  • падение FPS

С worker:

  • UI остаётся отзывчивым
  • операции распределяются по ядрам CPU
  • возможна параллельная обработка нескольких потоков данных

Типичные ошибки при проектировании

  • передача больших данных без transfer list
  • попытка использовать DOM внутри worker
  • повторное использование IV в AES-GCM
  • хранение CryptoKey без контроля жизненного цикла
  • отсутствие индексации чанков при параллельной обработке

Комбинирование Web Workers и Web Crypto API в архитектуре приложения

Часто используется следующая структура:

  • main thread: UI + координация
  • worker pool: криптография
  • service worker (опционально): кеширование и офлайн-доступ
  • IndexedDB: хранение зашифрованных данных

Такая архитектура характерна для:

  • end-to-end encrypted мессенджеров
  • браузерных менеджеров паролей
  • локальных криптографических инструментов
  • secure file storage систем