Генерация случайных чисел в Python: модуль random и основы безопасности

Курс охватывает работу с модулем random в Python, начиная от алгоритмов псевдослучайности и заканчивая практическим применением в играх и статистике. Вы также изучите ограничения стандартной библиотеки и научитесь использовать криптографически стойкие альтернативы для защиты данных.

1. Основы псевдослучайности и функция seed

Основы псевдослучайности и функция seed

Случайность — это фундаментальная концепция, которая делает компьютерные игры непредсказуемыми, криптографию надежной, а научные симуляции — реалистичными. Когда монстр в ролевой игре выпадает из сундука с вероятностью 5%, или когда алгоритм перемешивает колоду карт, за кулисами работает генератор случайных чисел. Однако компьютеры по своей природе абсолютно детерминированы: они строго выполняют инструкции шаг за шагом. Как же устройство, лишенное свободы воли, может создать нечто случайное?

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

Истинная случайность против псевдослучайности

Истинно случайные числа (True Random Numbers) генерируются на основе физических процессов окружающего мира, которые невозможно предсказать. Аппаратные генераторы измеряют такие явления, как атмосферный шум, тепловое излучение процессора или даже радиоактивный распад.

Например, один из первых подобных аппаратов, созданный в 1957 году, использовал неоновые трубки. Электроны внутри них двигались хаотично, создавая шум, который компьютер преобразовывал в числа. Сегодня подобные системы используются в военной криптографии и банковском секторе, где предсказуемость равносильна катастрофе.

Однако для большинства повседневных задач, таких как игры или статистика, аппаратные генераторы слишком медленны и дороги. Здесь на помощь приходят псевдослучайные числа (Pseudo-Random Numbers).

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

!Схема работы генераторов случайных чисел

Математика иллюзии: Линейный конгруэнтный метод

Чтобы понять, как математика создает иллюзию хаоса, рассмотрим один из старейших алгоритмов — линейный конгруэнтный генератор (LCG). Он вычисляет каждое следующее случайное число на основе предыдущего по следующей формуле:

Где:

  • — новое случайное число.
  • — предыдущее случайное число.
  • — множитель (постоянное целое число).
  • — приращение (постоянное целое число).
  • — модуль (определяет максимальное значение числа).
  • — математическая операция получения остатка от деления на .
  • Представим, что мы выбрали параметры: , , . Нам нужна начальная точка, чтобы запустить формулу. Пусть первое число .

    Считаем шаг за шагом: 1. 2. 3. 4.

    Мы получили последовательность: 3, 4, 1, 0. Для стороннего наблюдателя, не знающего формулу, эти числа могут показаться случайными. Но на самом деле это строгая математическая цепочка. Современный модуль random в Python использует гораздо более сложный алгоритм — Вихрь Мерсенна (Mersenne Twister), но базовый принцип остается тем же: следующее число всегда вычисляется из предыдущего.

    Функция seed: управление хаосом

    В примере с формулой LCG мы использовали начальное значение . В программировании это начальное значение называется зерном или сидом (seed).

    Зерно — это отправная точка для алгоритма генерации. Если передать алгоритму одно и то же зерно, он выдаст абсолютно идентичную последовательность чисел.

    По умолчанию, когда вы импортируете модуль random в Python, он автоматически устанавливает зерно, используя текущее системное время компьютера (с точностью до микросекунд). Поскольку время постоянно меняется, при каждом запуске программы вы получаете новую последовательность чисел.

    Но Python позволяет установить это значение вручную с помощью функции random.seed().

    Если вы перезапустите этот код на своем компьютере, вы получите точно такие же числа: 0.6394... и 0.0250....

    !Интерактивный симулятор генератора псевдослучайных чисел

    Зачем нужна воспроизводимость?

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

    1. Отладка программ и поиск ошибок Представьте, что вы разрабатываете карточную игру. Во время тестирования игра вылетает с ошибкой, но только при определенной раздаче карт. Если карты перемешиваются истинно случайно, вы можете потратить часы, пытаясь воссоздать ту самую комбинацию, которая вызвала сбой. Если же вы используете фиксированный seed, раздача всегда будет одинаковой, и вы сможете спокойно найти и исправить ошибку в коде.

    2. Процедурная генерация в играх В популярной игре Minecraft миры генерируются случайным образом. Ландшафт, горы, реки и деревни создаются алгоритмом. Однако каждый мир имеет свой уникальный номер — тот самый seed. Если вы найдете красивый мир и передадите его seed другу, игра на его компьютере сгенерирует абсолютно идентичную планету, гора к горе, дерево к дереву.

    3. Научные исследования и машинное обучение В статистике и анализе данных часто требуется случайным образом разделить данные на обучающую и тестовую выборки. Устанавливая random.seed(), ученый гарантирует, что другие исследователи смогут запустить его код и получить точно такие же результаты, подтвердив правильность эксперимента.

    Базовые функции модуля random

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

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

    Если вам нужны целые числа, используется random.randint(a, b). Она возвращает случайное целое число , такое что . Обратите внимание, что обе границы включены в диапазон.

    Для работы с последовательностями (списками, кортежами) есть функция random.choice(), которая выбирает один случайный элемент.

    Ограничения безопасности: когда random опасен

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

    Поскольку алгоритм детерминирован, он абсолютно не подходит для криптографии. Никогда не используйте модуль random для генерации:

  • Паролей пользователей
  • Токенов авторизации
  • Ключей шифрования
  • Секретных ссылок для сброса пароля
  • Если злоумышленник узнает алгоритм (а в Python это открытый стандартный Вихрь Мерсенна) и сможет угадать начальное зерно (например, зная время, когда был сгенерирован токен), он сможет вычислить все предыдущие и последующие «случайные» числа. Это позволит ему предсказать пароли других пользователей или подделать сессионные ключи.

    Для задач, связанных с безопасностью, в Python существует отдельный модуль — secrets. Он использует криптографически стойкие генераторы псевдослучайных чисел (CSPRNG), которые собирают энтропию (случайный шум) из операционной системы.

    | Характеристика | Модуль random | Модуль secrets | | :--- | :--- | :--- | | Скорость работы | Очень высокая | Относительно медленная | | Воспроизводимость | Да (через seed) | Нет (невозможно установить seed) | | Предсказуемость | Да (если известно зерно) | Нет (криптографически защищено) | | Идеально для... | Игр, симуляций, тестов | Паролей, токенов, шифрования |

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

    2. Генерация чисел для игр и симуляций

    Генерация чисел для игр и симуляций

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

    Создание игр, симуляция физических процессов и статистическое моделирование — это те сферы, где модуль random раскрывает свой потенциал на 100%. В этой статье мы разберем, как генерировать числа для разных механик, управлять вероятностями и работать с последовательностями данных.

    Базовая генерация: кубики, координаты и урон

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

    Целые числа: random.randint и random.randrange

    Функция random.randint(a, b) — это ваш виртуальный многогранный кубик. Она возвращает случайное целое число , которое находится в диапазоне . Важнейшая деталь: обе границы включены в возможный результат.

    Где это применяется?

  • Бросок кубиков в настольных играх.
  • Расчет базового урона в ролевых играх (RPG).
  • Выбор случайного количества золота в сундуке.
  • Если вам нужен шаг (например, генерировать только четные числа), используется random.randrange(start, stop, step). В отличие от randint, верхняя граница stop здесь не включается в результат.

    Вещественные числа: random.uniform

    Когда речь заходит о физике, координатах или времени, целых чисел недостаточно. Функция random.uniform(a, b) возвращает случайное число с плавающей точкой в диапазоне .

    Представьте, что вы программируете появление метеорита на экране шириной 800 пикселей. Метеорит не обязан падать ровно на 100-м или 101-м пикселе, он может появиться на координате 100.452.

    Магия последовательностей: лут, карты и враги

    В играх мы редко работаем с голыми числами. Чаще всего нам нужно выбрать случайный объект из уже существующего набора: вытянуть карту из колоды, определить тип появившегося врага или выдать награду. Для этого в Python есть четыре мощных инструмента.

    !Методы работы с последовательностями

    1. Выбор одного элемента: random.choice

    Функция random.choice(seq) принимает любую непустую последовательность (список, кортеж, строку) и возвращает один случайный элемент.

    2. Взвешенный выбор: random.choices

    В примере выше шанс встретить Дракона равен шансу встретить Гоблина (25%). В реальных играх так не бывает: обычные враги встречаются часто, а боссы — редко. То же самое касается выпадения предметов (лута).

    Для управления вероятностями используется random.choices(population, weights, k).

  • weights — это список весов (шансов) для каждого элемента.
  • k — количество элементов, которые нужно выбрать.
  • Примечание: Функция choices делает выбор с возвращением. Это значит, что если вы попросите выбрать 3 предмета (k=3), один и тот же предмет может выпасть несколько раз.

    3. Уникальная выборка: random.sample

    Если вам нужно выбрать несколько элементов, но без повторений (выборка без возвращения), используйте random.sample(population, k).

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

    4. Перемешивание: random.shuffle

    Иногда нужно не выбрать элементы, а изменить их порядок. Функция random.shuffle(seq) перемешивает элементы списка случайным образом.

    Важное правило: эта функция изменяет сам список на месте (in-place) и ничего не возвращает. Она работает только с изменяемыми типами данных (списками), но не с кортежами или строками.

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

    Случайность в статистике и симуляциях

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

    Равномерное распределение против нормального

    Функции randint и uniform используют равномерное распределение. Это значит, что любое число в заданном диапазоне имеет абсолютно одинаковый шанс на выпадение. Если вы бросаете идеальный шестигранный кубик 6000 раз, каждая грань выпадет примерно по 1000 раз.

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

    Для симуляции таких процессов используется функция random.gauss(mu, sigma).

    Где:

  • (мю) — среднее значение (центр распределения).
  • (сигма) — стандартное отклонение (насколько сильно данные разбросаны от центра).
  • Представьте, что вы генерируете толпу NPC (неигровых персонажей) для симулятора города. Если вы используете random.uniform(150, 200) для их роста, у вас будет одинаковое количество людей ростом 150 см и 175 см. Это выглядит неестественно.

    Используем нормальное распределение:

    При таких параметрах около 68% всех сгенерированных NPC будут иметь рост от 168 до 182 см (), и лишь около 0.1% будут ниже 154 см или выше 196 см. Толпа будет выглядеть реалистично.

    !Симулятор распределения вероятностей

    Границы применимости: когда random не подходит

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

    Однако важно помнить главное правило, озвученное в предыдущей статье: алгоритм Вихрь Мерсенна, лежащий в основе модуля, предсказуем.

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

    Для любых задач, где предсказуемость ведет к финансовым потерям или угрозе безопасности (казино, криптография, токены сессий), необходимо использовать криптографически стойкие генераторы, такие как встроенный в Python модуль secrets. Он работает медленнее, не поддерживает установку seed для воспроизводимости, но гарантирует, что последовательность невозможно угадать.

    3. Выбор элементов и перемешивание последовательностей

    Выбор элементов и перемешивание последовательностей

    В программировании мы редко работаем с изолированными числами. Гораздо чаще нам приходится иметь дело с коллекциями данных: списками пользователей, колодами карт, инвентарем персонажа или наборами тестовых вопросов. Умение грамотно извлекать случайные элементы из этих коллекций и перемешивать их — базовый навык, который необходим для создания игр, проведения статистических выборок и написания автоматизированных тестов.

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

    Извлечение одного элемента: random.choice

    Самая частая задача — выбрать ровно один случайный элемент из существующего набора. Представьте, что вам нужно определить случайного победителя розыгрыша из списка участников или выбрать случайное слово для игры «Виселица».

    Начинающие программисты часто решают эту задачу в два этапа: сначала генерируют случайное число в пределах длины списка, а затем обращаются к элементу по этому индексу.

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

    В Python есть более изящное решение — функция random.choice(seq). Она принимает любую непустую последовательность (список, кортеж или строку) и возвращает один случайный элемент. Вероятность выбора каждого элемента абсолютно одинакова.

    Если передать в random.choice() пустую последовательность, Python выдаст ошибку IndexError: Cannot choose from an empty sequence. Поэтому перед выбором всегда убеждайтесь, что ваш список не пуст.

    Взвешенный выбор: random.choices

    Функция choice идеальна, когда все элементы равны. Но в реальном мире вероятности часто смещены. В видеоиграх обычный меч выпадает из сундука гораздо чаще, чем легендарный артефакт. В маркетинговых симуляциях пользователи кликают на красную кнопку чаще, чем на серую.

    Для управления вероятностями используется функция random.choices(population, weights=None, cum_weights=None, k=1).

    Разберем ее параметры:

  • population — список, из которого мы выбираем.
  • weights — список относительных весов (шансов) для каждого элемента.
  • k — количество элементов, которые нужно извлечь.
  • Важнейшая особенность random.choices заключается в том, что это выборка с возвращением. Это означает, что после того как элемент выбран, он «возвращается» обратно в корзину и может быть выбран снова. Если вы попросите выбрать 5 элементов (k=5), один и тот же предмет может выпасть все 5 раз.

    Веса не обязаны в сумме давать или . Python автоматически сложит все переданные значения и вычислит долю каждого. Если сумма весов равна , а вес элемента равен , то вероятность его выпадения составит (или ).

    !Интерактивный симулятор лутбокса

    Кумулятивные веса

    Вместо параметра weights можно использовать cum_weights (кумулятивные, или накопленные веса). В этом случае каждый следующий вес включает в себя сумму всех предыдущих.

    Например, веса weights=[10, 15, 25] эквивалентны кумулятивным весам cum_weights=[10, 25, 50]. Использование кумулятивных весов немного ускоряет работу алгоритма, если вам нужно делать миллионы выборок, так как Python не тратит время на их вычисление под капотом.

    Уникальная выборка: random.sample

    Что делать, если нам нужно выбрать несколько элементов, но без повторений? Например, сдать 5 карт из колоды или выбрать 6 выигрышных номеров в лотерее. Если мы используем choices, нам может выпасть два туза пик, что разрушит логику игры.

    Для выборки без возвращения применяется функция random.sample(population, k).

    Функция sample гарантирует, что все выбранные элементы будут уникальными (при условии, что исходный список не содержал дубликатов).

    Здесь есть строгое математическое ограничение: размер выборки не может превышать размер исходной популяции (). Если вы попытаетесь выбрать 10 карт из колоды, в которой осталось всего 5 карт, Python выбросит исключение ValueError.

    !Разница между выборкой с возвращением и без

    Особенности работы с множествами (sets)

    Исторически random.sample позволял передавать в качестве популяции множества (set). Однако, начиная с версии Python 3.9, эта возможность была признана устаревшей (вызывает DeprecationWarning), а в более новых версиях приводит к ошибке TypeError.

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

    Параметр counts в random.sample

    Начиная с Python 3.9, в random.sample был добавлен параметр counts, который позволяет указать количество повторений каждого элемента в исходной популяции, не дублируя их в самом списке физически.

    Перемешивание: random.shuffle

    Иногда задача состоит не в том, чтобы выбрать элементы, а в том, чтобы изменить их порядок на случайный. Для этого используется функция random.shuffle(x).

    Главная особенность этой функции — она изменяет переданную последовательность на месте (in-place) и возвращает None. Это сделано для экономии памяти: вместо создания новой копии списка, Python просто переставляет элементы в существующем.

    Поскольку shuffle изменяет объект напрямую, она работает только с изменяемыми (mutable) последовательностями, такими как списки. Если вы попытаетесь перемешать кортеж (tuple) или строку (str), программа завершится с ошибкой TypeError: 'tuple' object does not support item assignment.

    Что делать, если вам нужно перемешать кортеж, не меняя исходный объект? Здесь на помощь приходит элегантный трюк с использованием random.sample. Если запросить выборку, размер которой равен размеру всей популяции, вы получите перемешанную копию:

    Ограничения безопасности: когда random опасен

    Мы подробно разобрали, как тасовать колоды и раздавать карты. Но здесь кроется критически важный нюанс, о котором забывают многие начинающие разработчики.

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

    Если вы создаете онлайн-казино, покер-рум на реальные деньги или генерируете токены для сброса паролей, использовать random.shuffle или random.choice категорически запрещено. Злоумышленник, наблюдая за несколькими раздачами карт, может вычислить внутреннее состояние генератора и со 100% точностью предсказать, какие карты лежат в колоде и кому они достанутся.

    > Стандартные генераторы псевдослучайных чисел не подходят для целей безопасности. Для криптографических задач всегда используйте модуль secrets. > > Официальная документация Python

    Для безопасного перемешивания и выбора элементов в Python встроен модуль secrets, который использует криптографически стойкие источники случайности операционной системы (например, /dev/urandom в Linux).

    У модуля secrets есть класс SystemRandom, который предоставляет тот же интерфейс, что и модуль random, но гарантирует непредсказуемость:

    Использование secrets.SystemRandom() работает медленнее, чем стандартный random, поэтому его не стоит применять для симуляции физики частиц или процедурной генерации ландшафтов в играх. Но там, где на кону стоят деньги, безопасность пользователей или конфиденциальность данных — это единственный правильный выбор.

    4. Случайность в статистике и математическом моделировании

    Случайность в статистике и математическом моделировании

    В предыдущих статьях мы рассматривали случайность как инструмент для создания непредсказуемости: мы бросали виртуальные кубики, тасовали колоды карт и распределяли редкий лут в играх. Однако в большой науке, инженерии и анализе данных генераторы псевдослучайных чисел (ГПСЧ) выполняют совершенно иную, гораздо более фундаментальную роль. Они помогают нам понимать и моделировать реальный мир.

    Когда математические уравнения становятся слишком сложными для точного аналитического решения, исследователи прибегают к симуляциям. В этой статье мы разберем, как модуль random в Python применяется для статистических расчетов, почему равномерного распределения недостаточно для описания природы, и где проходят границы применимости стандартных алгоритмов.

    Метод Монте-Карло: случайность вместо уравнений

    Одним из самых красивых применений случайности в математике является метод Монте-Карло. Это класс вычислительных алгоритмов, которые используют многократное случайное тестирование для решения детерминированных (не случайных) задач.

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

    Классический пример — вычисление числа Пи (). Представим квадрат со стороной , в который вписан круг радиусом .

    Площадь круга вычисляется по формуле:

    Где — площадь круга, — математическая константа, — радиус круга.

    Площадь квадрата равна:

    Где — площадь квадрата, — половина стороны квадрата.

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

    Реализуем это на Python:

    Чем больше точек мы генерируем, тем точнее становится результат. Этот принцип называется законом больших чисел. Метод Монте-Карло используется повсеместно: от оценки финансовых рисков на Уолл-стрит до моделирования поведения элементарных частиц в ядерной физике.

    !Интерактивная симуляция метода Монте-Карло

    Распределения: как смоделировать реальность

    Функции вроде random.randint или random.uniform генерируют числа на основе равномерного распределения. Это означает, что любое число в заданном диапазоне имеет абсолютно одинаковый шанс на выпадение. Если вы бросаете шестигранный кубик, шанс получить единицу точно такой же, как шанс получить шестерку.

    Но реальный мир устроен иначе. Если вы выйдете на улицу и измерите рост тысячи случайных прохожих, вы не получите равномерного распределения. Людей с ростом 175 см будет очень много, а людей с ростом 140 см или 210 см — единицы.

    Нормальное распределение (Гауссиана)

    Для моделирования природных и социальных явлений (рост, вес, уровень IQ, погрешности измерений) используется нормальное распределение. На графике оно выглядит как симметричный колокол, где большинство значений группируется вокруг центра, а к краям их количество резко падает.

    В статистике положение значения в нормальном распределении часто описывается через стандартную оценку:

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

    В модуле random для генерации таких чисел используется функция random.gauss(mu, sigma).

    Представьте, что вы разрабатываете ролевую игру и хотите сгенерировать рост для 1000 неигровых персонажей (NPC). Средний рост () пусть будет 175 см, а стандартное отклонение () — 7 см.

    Благодаря правилу «трех сигм» (68-95-99.7), мы можем быть уверены, что:

  • Около 68% персонажей будут иметь рост от 168 до 182 см ().
  • Около 95% — от 161 до 189 см ().
  • И только 0.3% персонажей окажутся экстремально низкими (ниже 154 см) или экстремально высокими (выше 196 см).
  • !График нормального распределения

    Использование random.gauss делает симуляции и игры гораздо более реалистичными. Если вы рассчитываете урон от оружия, равномерный разброс от 10 до 50 сделает бои слишком непредсказуемыми. Нормальное распределение со средним 30 обеспечит стабильный урон в большинстве атак, оставляя редкий шанс на критический провал или невероятный успех.

    Экспоненциальное распределение: ожидание событий

    Еще одна частая задача в моделировании — расчет времени между независимыми событиями. Как часто клиенты заходят в банк? С какими интервалами пользователи отправляют запросы на веб-сервер? Как быстро распадаются радиоактивные атомы?

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

    Математическое ожидание времени между событиями вычисляется как:

    Где — ожидаемое среднее время между событиями, а (лямбда) — интенсивность процесса (среднее количество событий за единицу времени).

    В Python для этого есть функция random.expovariate(lambd).

    Смоделируем работу колл-центра. Допустим, в среднем поступает 5 звонков в минуту. Значит, . Среднее время между звонками должно составить минуты (или 12 секунд).

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

    Ограничения псевдослучайности в науке и безопасности

    Мы увидели, насколько мощным инструментом является модуль random. Однако при переходе к серьезным научным исследованиям или задачам информационной безопасности необходимо понимать его фундаментальные ограничения.

    > Строка называется случайной тогда и только тогда, когда она короче любой компьютерной программы, способной её воспроизвести. > > habr.com

    Как мы обсуждали в первой статье курса, Python использует алгоритм Вихрь Мерсенна (Mersenne Twister, MT19937). Это детерминированный алгоритм. Его внутреннее состояние состоит из массива всего в 624 32-битных целых числа.

    Проблема для криптографии

    В криптографии (генерация паролей, токенов сессий, ключей шифрования) предсказуемость равносильна взлому. Если злоумышленник наблюдает за выводами стандартного модуля random, ему достаточно собрать 624 последовательных числа. С помощью несложных математических операций (обращения процесса «темперинга») он может полностью восстановить внутреннее состояние генератора.

    Как только массив из 624 чисел известен, хакер может со 100% точностью предсказать все будущие и прошлые числа, которые выдаст программа. Именно поэтому для любых задач, связанных с безопасностью или реальными деньгами (например, в онлайн-казино), необходимо использовать криптографически стойкий модуль secrets, который опирается на аппаратные источники энтропии операционной системы.

    Проблема для больших данных

    Для большинства бытовых симуляций Вихрь Мерсенна идеален: он быстр и имеет колоссальный период повторения (). Однако в современной науке о данных, когда исследователи запускают симуляции климата или обучают нейросети, потребляя миллиарды случайных чисел в секунду, проявляются две проблемы:

  • Скорость: Стандартный модуль random в Python работает слишком медленно для массивов из миллионов элементов, так как генерирует числа по одному в цикле.
  • Статистические артефакты: При огромных объемах данных в многомерных пространствах Вихрь Мерсенна начинает показывать микроскопические закономерности, которые могут исказить результаты тонких научных симуляций.
  • Поэтому в профессиональной аналитике данных и машинном обучении стандартом де-факто является библиотека numpy. Ее модуль numpy.random не только умеет генерировать целые массивы чисел за доли секунды благодаря оптимизации на языке C, но и использует более современные генераторы псевдослучайных чисел (например, PCG64), которые обладают лучшими статистическими свойствами для параллельных вычислений.

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

    5. Безопасная генерация данных и основы криптографии

    Безопасная генерация данных и основы криптографии

    В предыдущих статьях мы научились использовать случайность для создания увлекательных игровых механик и проведения сложных математических симуляций. Мы выяснили, что предсказуемость генератора псевдослучайных чисел (ГПСЧ) — это полезное свойство, позволяющее воспроизводить результаты с помощью функции seed.

    Однако в мире информационной безопасности предсказуемость — это главный враг. Когда речь заходит о генерации паролей, токенов для сброса пароля, ключей шифрования или идентификаторов банковских сессий, использование стандартного модуля random становится критической уязвимостью. В этой статье мы разберем, почему алгоритмы вроде Вихря Мерсенна опасны для криптографии, откуда компьютеры берут настоящую случайность и как правильно использовать модуль secrets в Python.

    Иллюзия случайности и уязвимость состояния

    Как мы уже знаем, стандартный модуль random в Python работает на базе алгоритма Вихрь Мерсенна (Mersenne Twister). Это детерминированный математический автомат. Он не бросает реальные кубики, а выполняет серию вычислений над своим внутренним состоянием.

    Внутреннее состояние Вихря Мерсенна состоит из массива, содержащего 624 целых 32-битных числа. Каждый раз, когда вы запрашиваете новое случайное число, алгоритм берет значения из этого массива, перемешивает их по строгой математической формуле (этот процесс называется темперингом) и выдает результат, параллельно обновляя само состояние.

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

    Представьте, что вы используете random.randint для генерации токенов сброса пароля на вашем сайте. Хакер запрашивает сброс пароля для своего аккаунта 624 раза подряд и записывает полученные токены. Восстановив состояние генератора, он сможет со 100% точностью предсказать, какой токен будет сгенерирован следующим. Затем он запрашивает сброс пароля для аккаунта администратора, локально вычисляет будущий токен и захватывает контроль над системой.

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

    Энтропия: откуда берется настоящая непредсказуемость

    Если математические формулы всегда предсказуемы, как компьютеру создать по-настоящему случайное число? Ответ кроется в физическом мире.

    Операционные системы (Windows, Linux, macOS) постоянно собирают данные о непредсказуемых физических событиях, происходящих внутри и вокруг компьютера. К таким событиям относятся:

  • Микросекундные задержки между нажатиями клавиш на клавиатуре.
  • Точные координаты и скорость движения мыши.
  • Флуктуации температуры процессора.
  • Шум в показаниях датчиков напряжения на материнской плате.
  • Все эти крошечные, хаотичные фрагменты данных собираются вместе и перемешиваются в специальном хранилище, которое называется пулом энтропии (от английского entropy pool).

    В информатике энтропия — это мера неопределенности или непредсказуемости информации. Чем выше энтропия, тем сложнее угадать данные. Когда программе требуется криптографически стойкое случайное число, она обращается к операционной системе (например, через файл /dev/urandom в Linux или функцию CryptGenRandom в Windows). ОС берет «шум» из пула энтропии и на его основе генерирует абсолютно непредсказуемые байты.

    !Схема сбора энтропии операционной системой

    Модуль secrets: криптография в Python

    Начиная с версии 3.6, в Python появился встроенный модуль secrets. Его главная задача — предоставить разработчикам простой и безопасный инструмент для генерации криптографически стойких данных. Под капотом secrets не использует Вихрь Мерсенна; вместо этого он напрямую запрашивает случайные байты у операционной системы через функцию os.urandom().

    Рассмотрим основные функции этого модуля и их практическое применение.

    Безопасный выбор и генерация чисел

    Если вам нужно выбрать случайный элемент из списка (например, символ для пароля) или сгенерировать число, используйте аналоги функций из модуля random, но внутри secrets:

  • secrets.choice(sequence) — безопасно выбирает один элемент из последовательности.
  • secrets.randbelow(n) — возвращает случайное целое число в диапазоне от до .
  • secrets.randbits(k) — генерирует целое число, состоящее из случайных бит.
  • Напишем функцию для генерации надежного пароля. Надежный пароль должен содержать буквы разного регистра, цифры и специальные символы.

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

    Где — энтропия в битах, — длина пароля, — размер алфавита (количество возможных уникальных символов).

    Если мы используем английские буквы (52), цифры (10) и спецсимволы (32), размер алфавита . Для пароля длиной 12 символов энтропия составит бит. Это считается хорошим уровнем защиты для повседневных аккаунтов.

    !Интерактивный калькулятор энтропии пароля

    Генерация токенов

    Для веб-разработки чаще всего нужны не просто числа, а длинные случайные строки — токены. Они используются для идентификации сессий (чтобы сайт «помнил», что вы вошли в аккаунт), создания ссылок для подтверждения email или API-ключей.

    Модуль secrets предлагает две удобные функции для этого:

  • secrets.token_hex(nbytes) — возвращает строку в шестнадцатеричном формате (содержит только цифры от 0 до 9 и буквы от a до f). Удобно для генерации ключей и идентификаторов.
  • secrets.token_urlsafe(nbytes) — возвращает строку, закодированную в формате Base64, которая безопасно передается в URL-адресах (не содержит пробелов, слешей и плюсов).
  • Параметр nbytes указывает количество случайных байт, а не итоговую длину строки. Каждый байт превращается примерно в 1.3 символа при кодировании.

    Для криптографических токенов рекомендуется использовать не менее 32 байт (256 бит) энтропии. Это делает подбор токена физически невозможным даже при использовании всех вычислительных мощностей на планете.

    Защита от атак по времени (Timing Attacks)

    Безопасная генерация — это только половина дела. Секретные данные нужно еще и безопасно проверять.

    Представьте, что вы написали код для проверки API-ключа:

    Стандартный оператор == в Python сравнивает строки посимвольно слева направо. Как только он находит первое несовпадение, он немедленно прекращает работу и возвращает False.

    Если первый символ не совпал, проверка займет, например, 1 микросекунду. Если совпали первые 10 символов, а 11-й оказался неверным, проверка займет 1.5 микросекунды. Хакер может отправлять миллионы запросов и с помощью высокоточного секундомера измерять время ответа сервера. Подбирая символ за символом и ориентируясь на микроскопические задержки, он сможет посимвольно восстановить секретный токен. Это называется атакой по времени (Timing Attack).

    Чтобы избежать этого, модуль secrets предоставляет функцию compare_digest(a, b). Она сравнивает две строки за абсолютно одинаковое время, независимо от того, на каком символе произошло несовпадение.

    Сравнение: random против secrets

    Чтобы закрепить материал, подведем итоги в виде таблицы, которая поможет вам быстро принимать решения при написании кода:

    | Характеристика | Модуль random | Модуль secrets | | :--- | :--- | :--- | | Источник случайности | Математический алгоритм (Вихрь Мерсенна) | Пул энтропии операционной системы | | Предсказуемость | Да, если известно внутреннее состояние | Нет, криптографически стоек | | Воспроизводимость | Да, с помощью функции seed() | Нет, каждый запуск уникален | | Скорость работы | Очень высокая | Относительно низкая (зависит от ОС) | | Идеально подходит для | Игр, симуляций, A/B тестирования, машинного обучения | Паролей, токенов, ключей шифрования, сессий |

    Использование правильного инструмента для правильной задачи — признак профессионального разработчика. Оставьте random для бросков виртуальных кубиков, а безопасность ваших пользователей доверьте модулю secrets и операционной системе.