Типизированные массивы для генерации случайных значений

Типизированные массивы являются ключевым инструментом при работе с криптографически стойкой случайностью в JavaScript, поскольку Web Crypto API оперирует исключительно бинарными буферами фиксированного размера. Основная функция, обеспечивающая доступ к криптографически безопасным случайным значениям, заполняет именно такие структуры, что делает понимание их устройства критически важным.

В основе лежит объект ArrayBuffer — непрерывная область памяти фиксированной длины, представляющая собой «сырые» байты без интерпретации. TypedArray создают над этим буфером представление с конкретной интерпретацией данных: как 8-битные, 16-битные или 32-битные числа, знаковые или беззнаковые.

Наиболее часто используемая структура для генерации случайных значений — Uint8Array. Каждый элемент массива представляет собой число от 0 до 255 (1 байт). Именно в таком виде Web Crypto API возвращает случайные данные.

const buffer = new Uint8Array(16);
crypto.getRandomValues(buffer);

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

Особенность Uint8Array заключается в прямом соответствии с байтами памяти: один элемент — один байт. Это делает его универсальной основой для дальнейших преобразований в числа других диапазонов.

Механизм crypto.getRandomValues

Функция crypto.getRandomValues() принимает любой числовой TypedArray и заполняет его случайными значениями. Поддерживаются:

  • Uint8Array
  • Uint16Array
  • Uint32Array
  • BigUint64Array
  • Int8Array, Int16Array, Int32Array
const arr = new Uint32Array(4);
crypto.getRandomValues(arr);

Важно, что API не возвращает новые данные, а модифицирует существующий массив. Это исключает лишние аллокации памяти и повышает производительность.

Uint16Array и Uint32Array для расширенного диапазона

Использование Uint8Array ограничивает диапазон значений. Для более широких диапазонов применяются Uint16Array и Uint32Array.

Uint16Array хранит значения от 0 до 65535:

const arr = new Uint16Array(8);
crypto.getRandomValues(arr);

Uint32Array позволяет получать числа от 0 до 4 294 967 295:

const arr = new Uint32Array(4);
crypto.getRandomValues(arr);

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

Проблема модуля и смещение распределения

Одна из наиболее распространённых ошибок — использование оператора остатка от деления для ограничения диапазона:

const arr = new Uint8Array(1);
crypto.getRandomValues(arr);

const value = arr[0] % 10;

Такой подход приводит к смещению распределения, поскольку 256 не делится на 10 без остатка. В результате некоторые значения будут встречаться чаще.

Корректный подход: отбрасывание лишних значений

Для устранения смещения применяется метод отбрасывания (rejection sampling):

function randomInt(min, max) {
  const range = max - min + 1;
  const maxValid = Math.floor(256 / range) * range;

  const buf = new Uint8Array(1);

  let value;
  do {
    crypto.getRandomValues(buf);
    value = buf[0];
  } while (value >= maxValid);

  return min + (value % range);
}

Этот метод гарантирует равномерное распределение без статистических искажений.

Генерация 32-битных случайных чисел

Uint32Array позволяет работать с более крупными диапазонами, но проблема смещения сохраняется при приведении к меньшим диапазонам.

const arr = new Uint32Array(1);
crypto.getRandomValues(arr);

const value = arr[0] % 1000;

Более корректная версия использует порог отсечения:

function randomInt32(max) {
  const arr = new Uint32Array(1);
  const limit = Math.floor(0xFFFFFFFF / max) * max;

  let x;
  do {
    crypto.getRandomValues(arr);
    x = arr[0];
  } while (x >= limit);

  return x % max;
}

Генерация чисел с плавающей точкой

Часто требуется получить значение в диапазоне [0, 1). Используется 32-битная энтропия:

function randomFloat() {
  const arr = new Uint32Array(1);
  crypto.getRandomValues(arr);

  return arr[0] / 2 ** 32;
}

Деление на 2^32 обеспечивает равномерное распределение по всей шкале double precision, исключая предсказуемые паттерны, характерные для Math.random.

Расширенный вариант с повышенной точностью использует 53 бита мантиссы:

function randomFloat53() {
  const arr = new Uint32Array(2);
  crypto.getRandomValues(arr);

  const high = arr[0] * 2 ** 32;
  const low = arr[1];

  return (high + low) / 2 ** 64;
}

BigUint64Array и работа с 64-битными значениями

BigUint64Array используется для генерации больших случайных чисел:

const arr = new BigUint64Array(2);
crypto.getRandomValues(arr);

Каждый элемент представляет собой 64-битное беззнаковое число. Это особенно полезно при криптографических протоколах, генерации ключей и идентификаторов.

Преобразование байтов в шестнадцатеричную строку

Типизированные массивы часто используются для формирования токенов и ключей в hex-формате:

function toHex(buffer) {
  return [...buffer]
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

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

const hex = toHex(bytes);

Такой подход широко применяется при генерации идентификаторов сессий и nonce.

Использование ArrayBuffer как общего хранилища

TypedArray могут разделять один и тот же буфер памяти:

const buffer = new ArrayBuffer(8);

const u8 = new Uint8Array(buffer);
const u32 = new Uint32Array(buffer);

Это позволяет интерпретировать одни и те же случайные данные в разных форматах. Например, заполнение через Uint8Array и чтение через Uint32Array.

Производительность и минимизация накладных расходов

Web Crypto API оптимизирован для работы с большими блоками данных. Использование TypedArray позволяет:

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

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

Безопасность и отличие от псевдослучайных генераторов

Math.random() не подходит для криптографических задач, поскольку:

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

Web Crypto API опирается на системный генератор случайных чисел, который использует аппаратные источники шума, события системы и криптографические преобразования.

Формирование массивов случайных структур

TypedArray позволяют формировать сложные структуры данных, например массив случайных координат:

const coords = new Float32Array(10);

const bytes = new Uint32Array(10);
crypto.getRandomValues(bytes);

for (let i = 0; i < coords.length; i++) {
  coords[i] = bytes[i] / 2 ** 32;
}

Такой подход используется в симуляциях, генерации шума и стохастических алгоритмах.

Ограничения диапазонов и корректное масштабирование

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

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

value = randomByte % max;

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

function randomRange(max) {
  const arr = new Uint32Array(1);
  const limit = Math.floor(0xFFFFFFFF / max) * max;

  let x;
  do {
    crypto.getRandomValues(arr);
    x = arr[0];
  } while (x >= limit);

  return x % max;
}

Использование TypedArray в криптографических протоколах

TypedArray являются базовой структурой для:

  • генерации ключей (AES, RSA, ECDSA)
  • создания nonce и IV
  • формирования salt в PBKDF2
  • генерации токенов доступа

Пример соли для PBKDF2:

const salt = new Uint8Array(16);
crypto.getRandomValues(salt);

Работа с памятью и представлениями данных

TypedArray обеспечивают несколько уровней интерпретации одного массива:

  • Uint8Array — побайтовое представление
  • DataView — гибкое чтение с контролем endian
  • Uint32Array — группировка по 4 байта

Это позволяет эффективно преобразовывать случайные данные без копирования.

const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);

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

const firstInt = view.getUint32(0);

Практика генерации UUID-подобных структур

UUID можно формировать напрямую через Uint8Array:

function uuidLike() {
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);

  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;

  return [...bytes]
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

Bitmasking выполняется через побитовые операции над элементами массива, что возможно благодаря типизированной структуре данных.

Особенности работы с выравниванием и архитектурой

TypedArray абстрагируют аппаратные особенности, но при использовании DataView может проявляться различие endianness. Web Crypto API гарантирует консистентность байтового потока, однако при ручной интерпретации важно учитывать порядок байтов.

Использование в высоконагруженных сценариях

При генерации больших объемов случайных данных предпочтительно:

  • переиспользовать буферы
  • избегать частых аллокаций
  • заполнять массивы большими блоками
const buffer = new Uint8Array(1024 * 1024);
crypto.getRandomValues(buffer);

Такой подход используется в криптографических сервисах, генераторах ключей и системах распределённого хеширования.