Почему нельзя переиспользовать nonce в AES-GCM

В режиме AES-GCM параметр nonce (в Web Crypto API он передаётся как iv) является критически важным элементом криптографической схемы. Он не является секретом, но обязан быть уникальным для каждого шифрования под одним и тем же ключом.

Nonce в GCM выполняет сразу две функции:

  • инициализирует счётчик для поточного шифрования
  • участвует в вычислении аутентификационного тега (authentication tag)

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


Как Web Crypto API использует AES-GCM

В браузерной реализации через SubtleCrypto шифрование выглядит так:

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

const iv = crypto.getRandomValues(new Uint8Array(12));

const ciphertext = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv
  },
  key,
  new TextEncoder().encode("секретные данные")
);

Здесь iv — это nonce. Обычно используется 12 байт, потому что это оптимальная длина для GCM.


Что происходит при повторном использовании nonce

Если один и тот же iv используется повторно с тем же ключом, нарушается базовое предположение безопасности режима GCM.

AES-GCM превращается из защищённого режима в уязвимый потоковый генератор, где повторяется один и тот же keystream.

C_1 = P_1 K ,C_2 = P_2 K

Если nonce повторяется, то keystream K становится одинаковым:

C_1 C_2 = (P_1 K) (P_2 K) = P_1 P_2

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


Восстановление исходных данных при повторе nonce

Если злоумышленнику известен один из открытых текстов или его часть, он может восстановить второй.

Например:

  • известно P1
  • перехвачены C1 и C2

Тогда:

K = C1 ⊕ P1
P2 = C2 ⊕ K

Это превращает AES-GCM в полностью предсказуемую схему при одной ошибке в управлении nonce.


Нарушение аутентичности (authentication tag)

GCM не только шифрует данные, но и защищает их от изменения через authentication tag.

При повторе nonce ломается и эта часть:

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

Фактически, повтор nonce превращает режим в неаутентифицированный потоковый шифр с возможностью фальсификации.


Почему случайный nonce не всегда безопасен

Частая ошибка в браузерных приложениях:

const iv = crypto.getRandomValues(new Uint8Array(12));

Кажется, что случайность решает проблему уникальности. Но это не гарантирует отсутствие коллизий.

Вероятность столкновения nonce растёт по принципу “парадокса дней рождения”:

p - e{-n2/(2^{96})}

Даже при миллионах сообщений вероятность становится значимой.


Правильные стратегии генерации nonce

Существует два безопасных подхода:

const counter = new Uint8Array(12);
crypto.subtle.encrypt({ name: "AES-GCM", iv: counter }, key, data);
counter[11]++;

Преимущество:

  • гарантированная уникальность
  • предсказуемость не влияет на безопасность AES-GCM

2. Комбинация random + counter

IV = random_prefix || monotonic_counter

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


Ошибки в реальных Web Crypto API приложениях

Повтор nonce из-за отсутствия хранения состояния

Частая ошибка:

  • nonce генерируется случайно
  • но не сохраняется вместе с ciphertext
  • при расшифровке создаётся заново

Это приводит к повторному использованию IV.


Использование Math.random

const iv = new Uint8Array(12).map(() => Math.random() * 256);

Это полностью ломает модель безопасности:

  • не криптографически стойкий генератор
  • возможны повторения
  • предсказуемость значений

Использование одного IV для всех сообщений

Иногда встречается попытка “оптимизации”:

const iv = new Uint8Array(12);

И дальнейшее повторное использование одного массива — это мгновенная катастрофа для безопасности.


Связь nonce и ключа в AES-GCM

Важно понимать:

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

Безопасность AES-GCM формально зависит от пары:

= f(, )

Нарушение уникальности nonce эквивалентно частичной компрометации ключа.


Последствия в реальных атаках

Повтор nonce приводит к следующим сценариям:

  • восстановление пользовательских сообщений
  • подмена API-запросов
  • подделка токенов авторизации
  • раскрытие зашифрованных cookie
  • атаки на TLS-подобные протоколы, если ошибка допущена на уровне приложения

Особенность Web Crypto API

crypto.subtle не проверяет уникальность iv.

API принимает:

  • повторяющиеся nonce
  • некорректную длину (в некоторых режимах)
  • логически небезопасные комбинации

Это сделано намеренно: криптография — ответственность разработчика, а не runtime.


Практическая модель безопасного использования

Корректная модель AES-GCM в браузере строится вокруг трёх правил:

  • nonce никогда не повторяется для одного ключа
  • nonce сохраняется вместе с ciphertext
  • генерация nonce не зависит от не-криптографических источников

Типовая структура безопасного сообщения

{
  iv: "...",
  ciphertext: "...",
  tag: "..."
}

Хотя Web Crypto API возвращает ciphertext вместе с tag, логика хранения должна учитывать связку с IV.


Причина фундаментальной уязвимости

AES-GCM использует потоковое шифрование внутри режима CTR.

Повтор nonce означает повтор keystream, а это ломает линейную модель:

C = P (IV)

Если IV повторяется, keystream повторяется полностью.


Ключевая инженерная ошибка

Главная проблема в реальных системах заключается не в криптографии, а в управлении состоянием:

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

Именно эти ошибки чаще всего приводят к компрометации AES-GCM в Web Crypto API приложениях.