Источники энтропии в Node.js

В криптографических системах, построенных на JavaScript, качество генерации случайных чисел напрямую определяет устойчивость ключей, nonce и всех производных механизмов. В SJCL (Stanford JavaScript Crypto Library) эта задача решается через абстрактный слой энтропии, который адаптируется под среду исполнения. В Node.js модель источников энтропии принципиально отличается от браузерной, поскольку отсутствуют пользовательские события, а основная роль переходит к операционной системе и криптографическим API рантайма.

В основе SJCL лежит генератор псевдослучайных чисел (PRNG), построенный на AES в счетчике (CTR mode). Однако криптографическая стойкость этого генератора полностью зависит от начального состояния — seed, который формируется из пула энтропии.

SJCL не пытается самостоятельно извлекать «истинную случайность», а агрегирует данные из доступных источников и затем смешивает их в внутреннем состоянии через хеш-функции (обычно SHA-256). Основной интерфейс накопления энтропии реализован через:

  • sjcl.random.addEntropy(data, estimatedEntropy, source)
  • внутренний пул энтропии (entropyPool)
  • механизм оценки качества источника

Ключевой момент: SJCL не доверяет одному источнику, а комбинирует несколько, увеличивая устойчивость к предсказуемости.

Основные источники энтропии в Node.js

В среде Node.js отсутствуют браузерные события (mousemove, keydown), поэтому SJCL переключается на системные и рантайм-источники.

Криптографический API операционной системы

Наиболее важный источник — встроенный модуль crypto:

  • crypto.randomBytes(size)
  • crypto.randomFillSync(buffer)
  • crypto.randomUUID() (в современных версиях Node.js)

Эти функции опираются на системный CSPRNG:

  • /dev/urandom или /dev/random в Unix-подобных системах
  • CryptGenRandom / BCryptGenRandom в Windows

SJCL при наличии Node-окружения часто использует именно этот источник как основной seed provider, обходя необходимость накопления энтропии вручную.

Особенность: crypto.randomBytes считается криптографически стойким и не требует дополнительной оценки энтропии со стороны SJCL.

Тайминговые источники

Даже при наличии криптографического API SJCL может дополнять пул энтропии данными из высокоточных таймеров:

  • process.hrtime.bigint()
  • process.hrtime()
  • Date.now()

Хотя эти значения предсказуемы в глобальном масштабе, их микроскопические вариации (задержки планировщика, нагрузка CPU, переключения контекста) добавляют шум в энтропийный пул.

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

Состояние процесса и памяти

Node.js предоставляет доступ к косвенным источникам вариативности:

  • адреса объектов в памяти (зависят от V8 heap layout)
  • состояние GC (garbage collector)
  • фрагментация кучи
  • порядок загрузки модулей

SJCL напрямую не использует эти данные, но при расширенных конфигурациях или кастомных entropy collector’ах они могут быть добавлены вручную через addEntropy.

Особенность V8: даже небольшие изменения в порядке аллокаций приводят к изменению указателей и смещений, что создает слабый, но полезный шумовой источник.

Сетевые и асинхронные события

Хотя Node.js не имеет пользовательского ввода, он насыщен асинхронными событиями:

  • завершение I/O операций
  • ответы сетевых запросов
  • таймеры setTimeout и setImmediate
  • очереди event loop

SJCL может использовать временные метки этих событий для добавления энтропии. Например:

  • время прихода TCP-пакета
  • задержка DNS-резолвинга
  • порядок выполнения промисов

Каждое такое событие содержит микровариации, связанные с состоянием системы в момент выполнения.

Системные источники ОС

На уровне операционной системы доступны дополнительные каналы:

  • аппаратный шум (энтропия драйверов устройств)
  • события прерываний CPU
  • jitter планировщика ядра
  • статистика загрузки системы

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

Инициализация PRNG в SJCL под Node.js

При запуске в Node.js SJCL обычно проходит следующую схему:

  1. Проверка наличия module.exports и окружения CommonJS
  2. Определение доступности crypto
  3. Если crypto.randomBytes существует — использование его как первичного seed
  4. Иначе — переход к fallback-методам (таймеры, ручная энтропия)

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

  • если есть криптографический API → мгновенная инициализация
  • если нет → накопление энтропии до порога (например, 256 бит)
  • после достижения порога → запуск PRNG

В Node.js на практике почти всегда используется первый вариант.

Модель оценки энтропии

SJCL не просто собирает данные, но и оценивает их качество. Каждому источнику присваивается вес (entropy estimate), выраженный в битах.

Примеры оценок:

  • crypto.randomBytes: ~256 бит на вызов (условно максимальный уровень)
  • process.hrtime: 1–4 бита на выборку
  • Date.now(): менее 1 бита (очень слабый источник)
  • события event loop: 2–8 бит в зависимости от контекста

Эти значения суммируются в пуле, и генератор не активируется, пока не достигнут минимальный порог (обычно 128 бит).

Fallback-режим без crypto

В средах, где crypto недоступен (редко в Node.js, но возможно в ограниченных сборках), SJCL переходит в деградированный режим:

  • сбор временных меток
  • использование счетчиков итераций
  • накопление асинхронного шума event loop

В этом режиме безопасность значительно ниже, так как отсутствует истинный аппаратный источник энтропии.

Роль event loop в формировании случайности

Event loop в Node.js создает уникальную структуру временных задержек:

  • фазы timers
  • I/O callbacks
  • idle, prepare
  • poll
  • check

Каждый цикл имеет непредсказуемую длительность из-за:

  • системной нагрузки
  • конкуренции потоков
  • планирования ОС

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

Практическая интеграция SJCL с Node.js crypto

В типичной интеграции происходит замена или обертка генератора:

  • переопределение sjcl.random.randomWords
  • использование crypto.randomBytes внутри кастомного collector
  • отключение браузерных источников

Пример логики уровня архитектуры:

  • SJCL запрашивает случайные слова
  • адаптер вызывает crypto.randomBytes
  • байты преобразуются в 32-битные слова
  • результат возвращается в PRNG

Такой подход фактически делает SJCL зависимым от OS CSPRNG, что является наиболее безопасной моделью.

Проблемы и ограничения энтропии в Node.js

Несмотря на высокий уровень криптографической поддержки, существуют нюансы:

  • холодный старт (early boot entropy starvation в ОС)
  • контейнерные среды с ограниченным /dev/random
  • виртуализированные системы с предсказуемыми seed
  • неправильное использование fallback-режимов

Особенно критичны сценарии, где разработчик отключает crypto и полагается на таймеры — это резко снижает стойкость ключей.

Расширение источников энтропии вручную

SJCL позволяет вручную добавлять энтропию, что используется в специфических сценариях:

  • серверы с повышенными требованиями к безопасности
  • изолированные среды
  • криптографические протоколы поверх Node.js

В таких случаях могут использоваться:

  • аппаратные RNG (HSM)
  • внешние сервисы энтропии
  • шум с датчиков (редко в Node.js контексте)

Данные добавляются через addEntropy, где разработчик обязан указывать оценку битовой энтропии.

Влияние V8 оптимизаций на случайность

V8 JIT-компилятор влияет на поведение системы:

  • инлайнинг функций меняет временные характеристики
  • оптимизации циклов изменяют timing jitter
  • garbage collector создает непредсказуемые паузы

Эти эффекты не предназначены как источник криптографической случайности, но косвенно увеличивают вариативность низкоэнтропийных источников.

Итоговая модель энтропии SJCL в Node.js

В совокупности система формируется как многослойная модель:

  • верхний уровень: crypto.randomBytes (OS CSPRNG)
  • средний уровень: таймеры и event loop jitter
  • нижний уровень: вспомогательные системные сигналы и состояние процесса

SJCL объединяет их в единый пул, после чего использует AES-CTR PRNG для генерации псевдослучайных последовательностей, опираясь на криптографически стойкое начальное состояние.