В режиме AES-GCM параметр nonce (в Web Crypto API он передаётся как
iv) является критически важным элементом криптографической
схемы. Он не является секретом, но обязан быть уникальным для каждого
шифрования под одним и тем же ключом.
Nonce в GCM выполняет сразу две функции:
При этом безопасность всей схемы держится не на случайности nonce, а именно на его уникальности.
В браузерной реализации через 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.
Если один и тот же 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
Это означает, что криптостойкость полностью разрушается: становится доступной связь между двумя исходными сообщениями.
Если злоумышленнику известен один из открытых текстов или его часть, он может восстановить второй.
Например:
P1C1 и C2Тогда:
K = C1 ⊕ P1
P2 = C2 ⊕ K
Это превращает AES-GCM в полностью предсказуемую схему при одной ошибке в управлении nonce.
GCM не только шифрует данные, но и защищает их от изменения через authentication tag.
При повторе nonce ломается и эта часть:
Фактически, повтор nonce превращает режим в неаутентифицированный потоковый шифр с возможностью фальсификации.
Частая ошибка в браузерных приложениях:
const iv = crypto.getRandomValues(new Uint8Array(12));
Кажется, что случайность решает проблему уникальности. Но это не гарантирует отсутствие коллизий.
Вероятность столкновения nonce растёт по принципу “парадокса дней рождения”:
p - e{-n2/(2^{96})}
Даже при миллионах сообщений вероятность становится значимой.
Существует два безопасных подхода:
const counter = new Uint8Array(12);
crypto.subtle.encrypt({ name: "AES-GCM", iv: counter }, key, data);
counter[11]++;
Преимущество:
IV = random_prefix || monotonic_counter
Такой подход используется в распределённых системах, где невозможно синхронизировать состояние счётчика.
Частая ошибка:
Это приводит к повторному использованию IV.
const iv = new Uint8Array(12).map(() => Math.random() * 256);
Это полностью ломает модель безопасности:
Иногда встречается попытка “оптимизации”:
const iv = new Uint8Array(12);
И дальнейшее повторное использование одного массива — это мгновенная катастрофа для безопасности.
Важно понимать:
Безопасность AES-GCM формально зависит от пары:
= f(, )
Нарушение уникальности nonce эквивалентно частичной компрометации ключа.
Повтор nonce приводит к следующим сценариям:
crypto.subtle не проверяет уникальность
iv.
API принимает:
Это сделано намеренно: криптография — ответственность разработчика, а не runtime.
Корректная модель AES-GCM в браузере строится вокруг трёх правил:
{
iv: "...",
ciphertext: "...",
tag: "..."
}
Хотя Web Crypto API возвращает ciphertext вместе с tag, логика хранения должна учитывать связку с IV.
AES-GCM использует потоковое шифрование внутри режима CTR.
Повтор nonce означает повтор keystream, а это ломает линейную модель:
C = P (IV)
Если IV повторяется, keystream повторяется
полностью.
Главная проблема в реальных системах заключается не в криптографии, а в управлении состоянием:
Именно эти ошибки чаще всего приводят к компрометации AES-GCM в Web Crypto API приложениях.