В TweetNaCl.js и nacl.js корректная работа со случайными байтами определяет безопасность ключей, nonce и всей криптографической схемы. Ошибки на этом уровне не проявляются сразу, но приводят к полной компрометации шифрования.
Ключевой принцип: все значения, используемые как ключи, nonce или соли, должны быть криптографически случайными и непредсказуемыми.
Одна из наиболее критичных ошибок — генерация байтов через
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 это приводит к тому, что:
В браузере и современных средах Node.js существует источник криптографически стойких случайных значений:
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
Типичная ошибка — смешивание подходов или fallback на небезопасные источники:
const crypto = window.crypto || Math.random; // критическая ошибка
Правильная модель:
crypto.getRandomValues — единственный допустимый
источник в браузереcrypto.randomBytesВ TweetNaCl.js nonce (вектор инициализации) является критическим
элементом для secretbox и box.
Ошибка:
const nonce = crypto.getRandomValues(new Uint8Array(24));
nacl.secretbox(msg, nonce, key1);
nacl.secretbox(msg2, nonce, key1); // повторное использование
Повтор nonce с одним ключом приводит к:
Правило:
TweetNaCl.js строго требует фиксированные размеры:
Частая ошибка — генерация “примерного” размера:
const key = crypto.getRandomValues(new Uint8Array(31)); // ошибка
или обрезка:
const key = crypto.getRandomValues(new Uint8Array(64)).slice(0, 32);
Проблема:
TypedArray в JavaScript передаёт данные по ссылке на
ArrayBuffer.
Ошибка:
const nonce = crypto.getRandomValues(new Uint8Array(24));
const a = nonce;
const b = nonce;
a[0] = 1;
Обе переменные указывают на один и тот же буфер.
В криптографии это приводит к:
Правильный подход — копирование:
const copy = new Uint8Array(nonce);
Иногда случайные байты получают из seed:
const seed = Date.now();
или:
const seed = userId;
Это полностью разрушает криптографическую стойкость.
Любая детерминированная генерация:
Часто случайные байты передаются через JSON:
JSON.stringify(crypto.getRandomValues(new Uint8Array(32)));
Проблемы:
Правильный способ:
const b64 = Buffer.from(bytes).toString('base64');
Типичный анти-паттерн:
const nonce = crypto.getRandomValues(new Uint8Array(24));
function encrypt(msg) {
return nacl.secretbox(msg, nonce, key);
}
Причина ошибки — nonce создаётся один раз и переиспользуется.
Последствия:
В server-side rendering часто встречается:
const bytes = new Uint8Array(32).fill(Math.random() * 255);
или попытки кэшировать случайные значения между запросами.
Проблема:
Разные среды имеют разные источники:
crypto.getRandomValuescrypto.randomBytesОшибка — считать, что API одинаково работает везде.
Часто случайные байты преобразуются в строку:
const s = String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)));
Проблемы:
Правильный подход:
Ошибка:
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 на тот же буфер.
Результат:
Алгоритмы nacl предполагают:
Любые “оптимизации” генерации случайных байтов: