Почему `Math.random` в JavaScript не подходит для криптографически стойких случайных чисел?

«Почему `Math.random` в JavaScript не подходит для криптографически стойких случайных чисел?» — вопрос из категории Безопасность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Math.random() в JavaScript генерирует псевдослучайные числа, которые не являются криптографически стойкими и поэтому не подходят для задач, требующих высокой степени безопасности.

Причины:

  1. Предсказуемость: Math.random() основан на детерминированных алгоритмах (Pseudo-Random Number Generators, PRNG), которые при знании начального состояния (seed) или достаточном количестве выходных данных могут быть предсказаны. Это делает их уязвимыми для атак, если злоумышленник может получить доступ к этим данным.
  2. Недостаточная энтропия: Источники энтропии, используемые Math.random(), обычно ограничены (например, системное время), что снижает непредсказуемость генерируемых чисел.

Сценарии, где Math.random() непригоден:

  • Генерация ключей шифрования.
  • Создание токенов сессий или аутентификации.
  • Генерация паролей или одноразовых кодов.
  • Создание уникальных идентификаторов (UUID) для защиты данных.
  • Любые другие задачи, где требуется высокая степень непредсказуемости и защиты от подбора.

Когда Math.random() подходит:

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

Альтернативы для криптографически стойких случайных чисел:

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

  • В браузерах (Web Crypto API): Используйте window.crypto.getRandomValues().
  • В Node.js: Используйте модуль crypto, например, crypto.randomBytes() или crypto.randomUUID().

Пример:

// НЕБЕЗОПАСНО: Math.random() - не для криптографических целей
const insecureRandom = Math.random();
console.log('Небезопасное случайное число (Math.random):', insecureRandom);

// БЕЗОПАСНО: Использование Web Crypto API для криптографически стойких случайных чисел
if (window.crypto && window.crypto.getRandomValues) {
    const array = new Uint32Array(1); // Создаем типизированный массив для хранения случайных чисел
    window.crypto.getRandomValues(array);
    const secureRandom = array[0] / (0xFFFFFFFF + 1); // Нормализация до диапазона [0, 1)
    console.log('Безопасное случайное число (crypto.getRandomValues):', secureRandom);

    // Пример генерации криптографически стойкого UUID (Node.js или современные браузеры)
    // console.log('Безопасный UUID:', crypto.randomUUID());
} else {
    console.warn('Web Crypto API не поддерживается в этом окружении.');
}

// Пример генерации случайных байтов (Node.js)
// const crypto = require('crypto');
// const secureBytes = crypto.randomBytes(16).toString('hex');
// console.log('Безопасные случайные байты (Node.js):', secureBytes);

Использование window.crypto.getRandomValues() гарантирует, что случайные числа генерируются с использованием системных источников энтропии, что делает их значительно более устойчивыми к предсказанию.