AES-CTR: потоковый режим, счётчик

CTR (Counter) режим относится к потоковым режимам шифрования и превращает блочный алгоритм AES в потоковый шифр, где каждый блок данных обрабатывается независимо, а результат зависит от комбинации ключа и счётчика.

Ключевая идея заключается в том, что вместо прямого шифрования блока данных AES используется результат шифрования специального значения — комбинации nonce и возрастающего счётчика. Полученный поток псевдослучайных байт затем применяется к данным через операцию XOR.


Алгоритм строится вокруг генерации ключевого потока:

  • берётся начальное значение (IV / nonce + counter block)
  • шифруется AES-алгоритмом
  • результат используется как поток ключевых байт
  • каждый блок данных XOR’ится с этим потоком
  • счётчик увеличивается на 1 для следующего блока

Формально:

plaintext ⊕ AES(key, counter_i) = ciphertext_i

Именно отсутствие прямой зависимости между блоками делает CTR режим параллелизуемым.


Структура параметров в Web Crypto API

В Web Crypto API режим AES-CTR задаётся через объект:

{
  name: "AES-CTR",
  counter: Uint8Array,
  length: number
}

counter

16-байтовый буфер, представляющий начальное состояние счётчика.

Он состоит из двух логических частей:

  • nonce (уникальная случайная часть)
  • сам счётчик (обычно младшие биты)

Важно: библиотека не генерирует nonce автоматически — его обязан задать разработчик.

length

Количество бит, отведённых под счётчик.

  • обычно 64
  • допустимо меньше или больше (до 128, но чаще 64)

Чем больше длина счётчика, тем меньше пространство nonce, и наоборот.


Создание ключа AES для CTR

WebCrypto требует явного создания ключа через SubtleCrypto.

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

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

  • длина ключа: 128, 192 или 256 бит
  • ключ симметричный (один и тот же для шифрования и расшифрования)
  • экспортируемость зависит от флага extractable

Шифрование данных

const encoder = new TextEncoder();

const data = encoder.encode("Секретное сообщение");

const counter = crypto.getRandomValues(new Uint8Array(16));

const encrypted = await crypto.subtle.encrypt(
  {
    name: "AES-CTR",
    counter: counter,
    length: 64
  },
  key,
  data
);

Результат:

  • ArrayBuffer с зашифрованными байтами
  • длина совпадает с исходными данными

Расшифрование

CTR не различает операции шифрования и расшифрования:

const decrypted = await crypto.subtle.decrypt(
  {
    name: "AES-CTR",
    counter: counter,
    length: 64
  },
  key,
  encrypted
);

const decoded = new TextDecoder().decode(decrypted);

Ключевые требования:

  • counter должен быть идентичным
  • key должен совпадать
  • length обязан быть тем же

Любое отклонение приводит к полностью некорректному результату.


Потоковая природа CTR

CTR режим можно рассматривать как генератор псевдослучайного потока байт:

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

Однако Web Crypto API не предоставляет встроенного stream API для AES-CTR, поэтому потоковую обработку приходится реализовывать вручную:

  • разбивать данные на чанки
  • следить за смещением счётчика
  • корректно продолжать поток

Критическая важность nonce и counter

Самая опасная ошибка при работе с CTR — повторное использование одинакового counter с тем же ключом.

Если:

  • key одинаковый
  • counter одинаковый

то ключевой поток полностью совпадает.

Это приводит к:

ciphertext1 ⊕ ciphertext2 = plaintext1 ⊕ plaintext2

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

Поэтому:

  • counter должен быть уникальным для каждого сообщения
  • часто используют случайный nonce + фиксированный счётчик

Практическая схема формирования counter

Обычно 16 байт разбиваются так:

  • первые 12 байт — nonce (случайные)
  • последние 4 байта — счётчик (0, 1, 2, …)

Пример:

const counter = new Uint8Array(16);
crypto.getRandomValues(counter.subarray(0, 12));

counter[15] = 1;

При шифровании больших данных счётчик увеличивается автоматически внутри алгоритма.


Параллелизм и производительность

CTR режим обладает важным свойством:

  • блоки независимы
  • возможно параллельное шифрование
  • хорошо масштабируется на CPU и GPU

Это делает его предпочтительным для:

  • потоковой передачи данных
  • больших файлов
  • сетевых протоколов

В отличие от CBC, где каждый блок зависит от предыдущего, CTR не имеет цепочки зависимостей.


Особенности реализации Web Crypto API

Несколько важных деталей:

  • API работает с ArrayBuffer, а не строками
  • нет встроенного управления состоянием счётчика между вызовами
  • каждое encrypt — независимая операция
  • разработчик обязан сам обеспечивать целостность потоков

Также стоит учитывать:

  • отсутствие встроенного padding (CTR его не использует)
  • отсутствие аутентификации данных (CTR не защищает от модификаций)

Безопасность и ограничения

CTR обеспечивает только конфиденциальность, но не целостность.

Это означает:

  • злоумышленник может менять биты в ciphertext
  • изменения предсказуемо отражаются в plaintext после расшифрования

Пример эффекта:

если изменить байт ciphertext, изменится ровно соответствующий байт plaintext

Поэтому CTR почти всегда комбинируется с:

  • HMAC
  • или AEAD режимами (например, AES-GCM)

Типичные ошибки при использовании

  1. Повторное использование counter
  2. Использование фиксированного nonce
  3. Несовпадение length при encrypt/decrypt
  4. Попытка использовать строки вместо ArrayBuffer
  5. Отсутствие контроля целостности данных
  6. Неправильная работа с частями потока данных

Когда CTR действительно уместен

Режим используется, когда требуется:

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

Он часто встречается в:

  • VPN-туннелях
  • системах передачи медиа
  • шифровании больших файлов

При этом почти всегда требует дополнительного слоя аутентификации данных.