nacl.randomBytes: источники энтропии в браузере и Node.js

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

В криптографии случайность не является абстрактным понятием. Она должна быть:

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

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

Браузерный источник энтропии

В среде браузера основной механизм получения криптографически стойких случайных значений — Web Crypto API:

  • window.crypto.getRandomValues

Этот интерфейс реализуется непосредственно браузером и использует энтропию операционной системы. Он гарантирует:

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

В типичной реализации TweetNaCl.js в браузере функция генерации случайных байтов выглядит концептуально так:

function randomBytes(n) {
  const array = new Uint8Array(n);
  crypto.getRandomValues(array);
  return array;
}

Ключевой момент заключается в том, что Web Crypto API:

  • не зависит от JavaScript-движка
  • не использует Math.random
  • изолирован от пользовательского кода
  • обновляется и обслуживается самим браузером и ОС

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

Node.js и системный генератор случайных чисел

В Node.js источником энтропии выступает модуль crypto, который опирается на системные CSPRNG (Cryptographically Secure Pseudo Random Number Generator).

Основной метод:

  • require('crypto').randomBytes

Принцип работы:

  • Node обращается к системному API (/dev/urandom в Linux, CryptGenRandom в Windows)
  • данные поступают из ядра ОС
  • JavaScript получает уже готовый буфер случайных байтов

Пример реализации:

const crypto = require('crypto');

function randomBytes(n) {
  return crypto.randomBytes(n);
}

В отличие от браузера, Node.js может возвращать Buffer, а не Uint8Array, хотя по сути это один и тот же бинарный массив с разной обёрткой.

Абстракция nacl.randomBytes в TweetNaCl.js

Библиотека TweetNaCl.js изначально спроектирована как минималистичная и не зависящая от окружения. Поэтому функция генерации случайных байтов не жёстко зашита, а определяется внешне.

Типичная архитектура:

  • если окружение браузерное → используется crypto.getRandomValues
  • если Node.js → используется crypto.randomBytes
  • если ничего нет → требуется ручная подстановка

Внутренне это выглядит как присваивание:

nacl.randomBytes = function (n) {
  // реализация зависит от среды
};

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

Критическая зависимость от качества источника

Все ключевые операции NaCl зависят от randomBytes:

  • генерация ключей публичного/секретного шифрования
  • создание nonce для symmetric encryption
  • формирование ephemeral keys
  • padding и salt значения в некоторых режимах

Ошибка в этом слое приводит к следующим последствиям:

  • повтор nonce при шифровании (катастрофа для stream ciphers)
  • предсказуемые ключи
  • восстановление сообщений через анализ повторяющихся потоков
  • деградация всей криптосистемы до уровня XOR-псевдошифрования

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

Важно различать:

Тип генератора Пример Применимость
CSPRNG crypto.getRandomValues криптография
PRNG Math.random только для UI/логики
LCG/MT внутренние алгоритмы моделирование

Использование Math.random в контексте nacl.randomBytes полностью разрушает безопасность, поскольку:

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

Энтропийные источники на уровне ОС

И браузер, и Node.js в конечном счёте используют энтропию операционной системы:

  • аппаратные шумы CPU
  • тайминги устройств ввода
  • сетевые прерывания
  • шум дисков и драйверов
  • специализированные аппаратные RNG (в современных CPU)

ОС агрегирует эти источники в пул энтропии, из которого CSPRNG извлекает данные.

Поведение в старых окружениях

Исторически существовали среды без Web Crypto API и без Node crypto:

  • старые браузеры (IE < 11)
  • ранние версии Node.js
  • изолированные WebView

В таких случаях библиотеки либо:

  • падают с ошибкой
  • либо используют fallback (опасный режим)

Типичный fallback:

function randomBytes(n) {
  const arr = new Array(n);
  for (let i = 0; i < n; i++) {
    arr[i] = (Math.random() * 256) | 0;
  }
  return new Uint8Array(arr);
}

Такой код категорически непригоден для криптографии, но иногда встречается в учебных или деградированных сборках.

Буферизация и производительность

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

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

Поэтому реализация:

  • должна быть синхронной (в большинстве случаев)
  • не должна блокировать event loop дольше, чем необходимо
  • должна избегать копирования данных

Node.js crypto.randomBytes оптимизирован на уровне C++ и работает значительно быстрее чисто JS-реализаций.

Важность уникальности nonce

В NaCl-архитектуре nonce не обязан быть секретным, но обязан быть уникальным. Поэтому nacl.randomBytes часто используется именно для:

  • 24-байтных nonce в XSalsa20-Poly1305
  • одноразовых идентификаторов сообщений

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

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

Практическая схема выбора источника

В реальных проектах обычно применяется проверка окружения:

const getRandomBytes = (n) => {
  if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
    const arr = new Uint8Array(n);
    crypto.getRandomValues(arr);
    return arr;
  }

  if (typeof require !== 'undefined') {
    return require('crypto').randomBytes(n);
  }

  throw new Error('No secure random source available');
};

Такая схема исключает деградированный режим.

Типичные ошибки интеграции

  1. Подмена randomBytes на Math.random

    • приводит к полной компрометации безопасности
  2. Кэширование случайных значений

    • повтор nonce
  3. Использование одного seed для генерации всех ключей

    • предсказуемость всей системы
  4. Игнорирование различий Buffer / Uint8Array

    • ошибки в бинарных операциях
  5. Ручная реализация PRNG

    • почти всегда криптографически слабее системного

Связь с архитектурой TweetNaCl.js

TweetNaCl.js минимизирует поверхность атаки, поэтому:

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

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

Поведение при отсутствии энтропии

Если системный источник временно недоступен:

  • браузер блокирует getRandomValues
  • Node.js может выбросить исключение
  • выполнение криптографических операций должно быть остановлено

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