Повторное использование nonce: последствия и защита

В криптографических примитивах NaCl и его JavaScript-реализации TweetNaCl.js (а также nacl.js как обёртках) nonce является критически важным элементом режима шифрования, обеспечивающим уникальность потока шифрования для каждого сообщения при использовании одного и того же ключа.

В конструкциях nacl.secretbox и nacl.box используется потоковое шифрование на базе XSalsa20 и аутентификация Poly1305. Это означает, что для каждого сообщения генерируется уникальный ключевой поток, зависящий от ключа и nonce. Любое повторное использование nonce с тем же ключом приводит к повторному использованию одного и того же ключевого потока.

В TweetNaCl.js nonce — это 24-байтовый массив (Uint8Array(24)), который передаётся вместе с сообщением:

  • nacl.secretbox(message, nonce, key)
  • nacl.box(message, nonce, publicKeyPair)

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

Ключевая идея: nonce не должен повторяться при шифровании разных сообщений одним ключом


Механизм возникновения уязвимости при повторе nonce

Шифрование в secretbox фактически строится как:

  • генерируется поток ключа: keystream = StreamCipher(key, nonce)
  • затем выполняется XOR: ciphertext = plaintext ⊕ keystream

Если nonce повторяется:

  • keystream₁ == keystream₂

Тогда для двух сообщений:

C1 = P1 ⊕ K
C2 = P2 ⊕ K

XOR двух шифртекстов даёт:

C1 ⊕ C2 = (P1 ⊕ K) ⊕ (P2 ⊕ K)
        = P1 ⊕ P2

Это полностью уничтожает конфиденциальность обоих сообщений.


Последствия повторного nonce

Восстановление информации о сообщениях

Даже без знания ключа возможно:

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

Если известно одно из сообщений (known-plaintext attack), второе восстанавливается полностью:

P2 = C1 ⊕ C2 ⊕ P1

Полное разрушение безопасности аутентификации

В nacl.secretbox используется Poly1305 MAC, ключ которого производен из того же потока.

Повтор nonce означает:

  • повтор MAC-ключа
  • возможность анализа тегов
  • потенциальная возможность подделки сообщений при наличии нескольких ciphertext’ов

Это критично: нарушается не только конфиденциальность, но и целостность.


Упрощение атак на протокол

В реальных протоколах повтор nonce приводит к:

  • атаке на сессионные сообщения
  • компрометации чатов и API-запросов
  • возможности восстановления токенов или JSON-структур

Типовые причины повторного nonce в JavaScript

Использование фиксированного nonce

const nonce = new Uint8Array(24); // всегда нули

Каждое новое сообщение повторяет keystream → катастрофическая уязвимость.


Ошибки генерации случайных значений

const nonce = new Uint8Array(24);
for (let i = 0; i < 24; i++) {
  nonce[i] = Math.floor(Math.random() * 256);
}

Проблемы:

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

Повтор nonce при перезапуске приложения

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

  • nonce увеличивается в памяти
  • после рестарта счётчик начинается заново

Использование timestamp без защиты

nonce.set(new Uint8Array(new BigUint64Array([Date.now()])));

Недостатки:

  • коллизии при высокой частоте запросов
  • предсказуемость

Особенности TweetNaCl.js / nacl.js

В TweetNaCl.js:

  • nonce для secretbox — 24 байта
  • nonce для box — также 24 байта
  • библиотека не проверяет уникальность nonce

Это принципиальное свойство:

безопасность полностью ложится на разработчика


Корректные модели использования nonce

Модель 1: криптографически стойкая случайность

Использование crypto.getRandomValues:

function createNonce() {
  const nonce = new Uint8Array(24);
  crypto.getRandomValues(nonce);
  return nonce;
}

Свойства:

  • вероятность коллизии пренебрежимо мала (2^192 пространство)
  • не требует хранения состояния

Модель 2: счётчик (deterministic nonce)

Используется в протоколах с состоянием:

let counter = 0n;

function createNonce() {
  const nonce = new Uint8Array(24);

  const view = new DataView(nonce.buffer);
  view.setBigUint64(16, counter, true);

  counter += 1n;
  return nonce;
}

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

  • гарантирует уникальность при корректном хранении состояния
  • требует персистентности между перезапусками

Модель 3: префикс + счётчик (распределённые системы)

const prefix = crypto.getRandomValues(new Uint8Array(16));
let counter = 0n;

function createNonce() {
  const nonce = new Uint8Array(24);
  nonce.set(prefix, 0);

  const view = new DataView(nonce.buffer);
  view.setBigUint64(16, counter, true);

  counter += 1n;
  return nonce;
}

Свойства:

  • предотвращает конфликты между инстансами
  • сохраняет детерминированность

Критические свойства безопасности nonce

Уникальность важнее случайности

Главное требование:

  • nonce не обязан быть непредсказуемым
  • nonce обязан быть уникальным

Это принципиальное различие:

  • IV в некоторых режимах требует случайности
  • nonce в NaCl требует уникальности

Повтор даже одного nonce ломает весь ключ

При повторе:

  • компрометируются все сообщения под этим ключом
  • безопасность не деградирует постепенно — она исчезает полностью

Невозможность «починить» последствия

Если nonce был повторён:

  • нельзя восстановить безопасность ключа
  • требуется смена ключа
  • все сообщения считаются скомпрометированными

Ошибки архитектуры, приводящие к повтору nonce

Stateless системы без центра генерации

  • несколько серверов генерируют nonce независимо
  • отсутствует глобальная синхронизация

Отсутствие хранения счётчика

  • генерация nonce «с нуля» при каждом запросе

Использование nonce как идентификатора

  • попытка привязать nonce к userId или messageId
  • приводит к предсказуемости и повторяемости

Практические модели защиты

Разделение ответственности

  • nonce генерируется отдельно от бизнес-логики
  • криптографический слой не зависит от приложения

Использование проверенных библиотек

В экосистеме NaCl:

  • libsodium обеспечивает безопасные nonce-паттерны
  • высокоуровневые API уменьшают риск ошибок

Жёсткое правило хранения состояния

Для счётчиков:

  • обязательная запись в persistent storage
  • атомарное обновление

Итоговые свойства угрозы повторного nonce

Повтор nonce в TweetNaCl.js:

  • раскрывает XOR сообщений
  • разрушает конфиденциальность
  • делает возможной частичную или полную реконструкцию данных
  • компрометирует аутентификацию Poly1305
  • приводит к полной утрате безопасности ключа

Безопасность конструкции nacl.secretbox и nacl.box полностью опирается на строгую дисциплину уникальности nonce, при которой любое отклонение превращает криптосистему в уязвимую потоковую схему с утечкой данных через простые алгебраические операции.