Типизированные массивы являются ключевым инструментом при работе с криптографически стойкой случайностью в 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() принимает любой
числовой TypedArray и заполняет его случайными значениями.
Поддерживаются:
const arr = new Uint32Array(4);
crypto.getRandomValues(arr);
Важно, что API не возвращает новые данные, а модифицирует существующий массив. Это исключает лишние аллокации памяти и повышает производительность.
Использование 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);
}
Этот метод гарантирует равномерное распределение без статистических искажений.
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 используется для генерации больших случайных чисел:
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.
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 являются базовой структурой для:
Пример соли для PBKDF2:
const salt = new Uint8Array(16);
crypto.getRandomValues(salt);
TypedArray обеспечивают несколько уровней интерпретации одного массива:
Это позволяет эффективно преобразовывать случайные данные без копирования.
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
const bytes = new Uint8Array(buffer);
crypto.getRandomValues(bytes);
const firstInt = view.getUint32(0);
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);
Такой подход используется в криптографических сервисах, генераторах ключей и системах распределённого хеширования.