Потоковая обработка данных: ReadableStream и шифрование

Потоковая обработка в веб-приложениях строится на идее поэтапного потребления данных без необходимости загружать весь объём в память. В контексте криптографии это особенно важно: шифрование больших файлов, медиа или сетевых потоков становится возможным без резких скачков потребления памяти и без блокировки основного потока выполнения.

Ключевая связка в современном браузере формируется из трёх компонентов:

  • ReadableStream — источник данных
  • TransformStream — промежуточная обработка
  • Web Crypto API (crypto.subtle) — криптографические операции

ReadableStream и устройство потоков данных

Web Streams API предоставляет абстракцию потокового чтения данных, где информация поступает чанками (chunks), а не единым массивом.

Базовая модель:

  • источник (файл, сеть, память)
  • контроллер потока
  • потребитель (reader)

Пример создания простого потока:

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode("Hello"));
    controller.enqueue(new TextEncoder().encode(" world"));
    controller.close();
  }
});

Чанки поступают последовательно, что позволяет обрабатывать данные без ожидания завершения передачи всего объёма.


TransformStream как слой обработки

TransformStream соединяет входной и выходной потоки и позволяет модифицировать данные на лету.

Архитектура:

  • readable → transform → writable

Простейшая форма:

const transform = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk);
  }
});

В контексте криптографии именно TransformStream становится местом, где выполняется шифрование или расшифрование каждого чанка.


Ограничения Web Crypto API в потоковой модели

Web Crypto API предоставляет интерфейс crypto.subtle, однако он не поддерживает нативное потоковое шифрование.

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

  • операции работают с ArrayBuffer целиком
  • отсутствует встроенная поддержка incremental encryption
  • требуется ручная сегментация данных

Это означает, что потоковая криптография реализуется как надстройка:

  • разбивка потока на чанки
  • шифрование каждого чанка отдельно
  • управление IV и состоянием

Потоковое шифрование с AES-GCM

На практике используется режим AES-GCM, обеспечивающий:

  • конфиденциальность
  • целостность данных
  • встроенный authentication tag

Ключ:

const key = await crypto.subtle.generateKey(
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["encrypt", "decrypt"]
);

Конструкция потокового шифрования

Основная идея: каждый чанк шифруется отдельно.

Проблема: AES-GCM требует уникального IV.

Решение: детерминированная генерация nonce.

Пример схемы:

  • базовый IV (12 байт)
  • индекс чанка
  • XOR или инкремент

TransformStream для шифрования

function createEncryptStream(key, baseIv) {
  let counter = 0;

  return new TransformStream({
    async transform(chunk, controller) {
      const iv = new Uint8Array(baseIv);
      iv[11] = counter & 0xff;
      iv[10] = (counter >> 8) & 0xff;

      const encrypted = await crypto.subtle.encrypt(
        {
          name: "AES-GCM",
          iv
        },
        key,
        chunk
      );

      counter++;
      controller.enqueue(new Uint8Array(encrypted));
    }
  });
}

Каждый блок обрабатывается независимо, что позволяет сохранять потоковую природу.


Потоковое расшифрование

Расшифрование симметрично:

function createDecryptStream(key, baseIv) {
  let counter = 0;

  return new TransformStream({
    async transform(chunk, controller) {
      const iv = new Uint8Array(baseIv);
      iv[11] = counter & 0xff;
      iv[10] = (counter >> 8) & 0xff;

      const decrypted = await crypto.subtle.decrypt(
        {
          name: "AES-GCM",
          iv
        },
        key,
        chunk
      );

      counter++;
      controller.enqueue(new Uint8Array(decrypted));
    }
  });
}

Связка потоков

Полная цепочка:

inputStream
  .pipeThrough(createEncryptStream(key, iv))
  .pipeTo(outputStream);

И обратная операция:

inputStream
  .pipeThrough(createDecryptStream(key, iv))
  .pipeTo(outputStream);

Управление IV и безопасностью

Критический аспект потокового шифрования — уникальность nonce.

Ошибки приводят к:

  • утечке ключа
  • разрушению целостности данных

Подходы:

  • глобальный random IV + счётчик
  • привязка IV к хешу файла
  • использование префикса и инкремента

Недопустимо:

  • повторное использование IV с тем же ключом

Разделение на чанки и границы блоков

Размер чанка влияет на:

  • производительность
  • задержки
  • нагрузку на GC

Практика:

  • 64 KB — универсальный баланс
  • 256 KB — для больших файлов
  • 16 KB — для low-latency потоков

Важно учитывать, что AES-GCM добавляет overhead (auth tag), увеличивая размер выходного потока.


Backpressure и управление нагрузкой

Web Streams API автоматически управляет backpressure через writable stream.

Если потребитель не успевает обрабатывать данные:

  • pipeline замедляется
  • transform не вызывает enqueue
  • память остаётся стабильной

Это критично при шифровании больших потоков, например:

  • видео
  • архивов
  • сетевых данных

Асинхронность и узкие места

crypto.subtle операции асинхронны, что приводит к:

  • микрозадержкам на каждом чанке
  • возможной деградации throughput

Оптимизации:

  • увеличение размера чанков
  • параллелизация (ограниченная)
  • batching перед encrypt

Обработка ошибок и целостность данных

AES-GCM обеспечивает проверку целостности, но потоковая модель требует дополнительного контроля:

  • ошибка в одном чанке должна прерывать поток
  • нельзя “пропускать” повреждённые блоки
  • необходим rollback или abort stream

Пример обработки:

new TransformStream({
  async transform(chunk, controller) {
    try {
      const encrypted = await crypto.subtle.encrypt(...);
      controller.enqueue(encrypted);
    } catch (e) {
      controller.error(e);
    }
  }
});

Архитектура потокового криптографического пайплайна

Типичная схема:

  1. ReadableStream (файл или сеть)
  2. TransformStream (шифрование)
  3. WritableStream (storage / upload)

или:

  1. network stream
  2. decrypt transform
  3. application consumer

Ключевой принцип — отсутствие буферизации всего объекта.


Потоковая криптография в реальных сценариях

Подобная модель применяется в:

  • защищённых загрузчиках файлов
  • end-to-end зашифрованных чатах
  • браузерных файловых хранилищах
  • потоковой передаче медиа

Главное преимущество — постоянное потребление памяти вне зависимости от размера данных.


Синхронизация состояния между потоками

При необходимости восстановления состояния (например, при прерывании загрузки) требуется:

  • сохранение counter IV
  • фиксация позиции потока
  • контроль целостности чанков

Это делает потоковую криптографию не только технической задачей, но и задачей протокольного уровня.