Распространённые ошибки при работе со случайными байтами

В TweetNaCl.js и nacl.js корректная работа со случайными байтами определяет безопасность ключей, nonce и всей криптографической схемы. Ошибки на этом уровне не проявляются сразу, но приводят к полной компрометации шифрования.

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


Использование Math.random вместо криптографического генератора

Одна из наиболее критичных ошибок — генерация байтов через Math.random().

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

Проблема заключается в том, что Math.random():

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

В контексте nacl.js это приводит к тому, что:

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

Игнорирование crypto.getRandomValues

В браузере и современных средах Node.js существует источник криптографически стойких случайных значений:

const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);

Типичная ошибка — смешивание подходов или fallback на небезопасные источники:

const crypto = window.crypto || Math.random; // критическая ошибка

Правильная модель:

  • crypto.getRandomValues — единственный допустимый источник в браузере
  • в Node.js — crypto.randomBytes

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

В TweetNaCl.js nonce (вектор инициализации) является критическим элементом для secretbox и box.

Ошибка:

const nonce = crypto.getRandomValues(new Uint8Array(24));

nacl.secretbox(msg, nonce, key1);
nacl.secretbox(msg2, nonce, key1); // повторное использование

Повтор nonce с одним ключом приводит к:

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

Правило:

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

Ошибки длины буферов

TweetNaCl.js строго требует фиксированные размеры:

  • ключ: 32 байта
  • nonce (secretbox): 24 байта
  • public key: 32 байта
  • signature: 64 байта

Частая ошибка — генерация “примерного” размера:

const key = crypto.getRandomValues(new Uint8Array(31)); // ошибка

или обрезка:

const key = crypto.getRandomValues(new Uint8Array(64)).slice(0, 32);

Проблема:

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

Повторное использование буферов TypedArray

TypedArray в JavaScript передаёт данные по ссылке на ArrayBuffer.

Ошибка:

const nonce = crypto.getRandomValues(new Uint8Array(24));

const a = nonce;
const b = nonce;

a[0] = 1;

Обе переменные указывают на один и тот же буфер.

В криптографии это приводит к:

  • неочевидным изменениям nonce
  • разрушению целостности шифрования
  • трудно воспроизводимым багам

Правильный подход — копирование:

const copy = new Uint8Array(nonce);

Использование предсказуемых seed-значений

Иногда случайные байты получают из seed:

const seed = Date.now();

или:

const seed = userId;

Это полностью разрушает криптографическую стойкость.

Любая детерминированная генерация:

  • упрощает перебор ключей
  • делает систему уязвимой к replay и precomputation атакам

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

Часто случайные байты передаются через JSON:

JSON.stringify(crypto.getRandomValues(new Uint8Array(32)));

Проблемы:

  • Uint8Array превращается в объект с индексами
  • теряется бинарная структура
  • возможны усечения и изменения типов

Правильный способ:

  • base64 или hex кодирование
const b64 = Buffer.from(bytes).toString('base64');

Повторное использование nonce при ошибочной архитектуре

Типичный анти-паттерн:

const nonce = crypto.getRandomValues(new Uint8Array(24));

function encrypt(msg) {
  return nacl.secretbox(msg, nonce, key);
}

Причина ошибки — nonce создаётся один раз и переиспользуется.

Последствия:

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

Неправильная работа с энтропией в SSR и браузере

В server-side rendering часто встречается:

const bytes = new Uint8Array(32).fill(Math.random() * 255);

или попытки кэшировать случайные значения между запросами.

Проблема:

  • SSR может повторно использовать состояние процесса
  • контейнеры могут клонироваться с одинаковым seed
  • возникает корреляция ключей между пользователями

Игнорирование источника случайности в средах выполнения

Разные среды имеют разные источники:

  • браузер → crypto.getRandomValues
  • Node.js → crypto.randomBytes
  • WebWorker → свой crypto контекст
  • старые окружения → отсутствует безопасный RNG

Ошибка — считать, что API одинаково работает везде.


Смешивание строк и байтов без контроля кодировки

Часто случайные байты преобразуются в строку:

const s = String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)));

Проблемы:

  • потеря значений > 255
  • UTF-16 искажения
  • неоднозначная декодировка

Правильный подход:

  • только бинарные форматы или base64/hex

Использование случайных байтов в качестве ключей без KDF

Ошибка:

const key = crypto.getRandomValues(new Uint8Array(32));

Если ключ генерируется из пользовательского ввода или слабого источника, необходимо использование KDF (например, Argon2, PBKDF2).

Без этого:

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

Преждевременное переиспользование буфера случайных данных

Ошибка:

const bytes = crypto.getRandomValues(new Uint8Array(32));

const key = bytes.subarray(0, 32);
bytes.fill(0);

subarray не копирует данные, а создаёт view на тот же буфер.

Результат:

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

Недооценка требований TweetNaCl.js к детерминизму входных данных

Алгоритмы nacl предполагают:

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

Любые “оптимизации” генерации случайных байтов:

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