Всем доброго времени суток. Я вдохновившись исследованием Irregular «vibe password generation», решил провести своё собственное, но уже с использованием российских LLM. Оно выйдет позже, когда я его закончу, а может когда вы это читаете оно уже вышло, но сейчас не об этом.
Так вот, разбираясь с генерацией паролей GigaChat'ом, я заметил следующую особенность - он не генерирует пароли самостоятельно, а создаёт внешний Python алгоритм, для генерации паролей. При одном и том же определённом промпте GigaChat выдаёт разные вариации генератора паролей. В одних случаях он использует модуль random, в других — secrets.
Обратив внимание, что сами пароли особого отличия не имели, я задался вопросом, а имеет ли значение, какой Python модуль используется? Копая глубже я понял, что стоит рассказать об этом вам.
В этой статье я последовательно разберу:
что такое энтропия пароля и где её неправильно понимают;
как работает криптографически безопасная генерация (
secrets);почему
randomдаёт лишь иллюзию надёжности;и покажу как я пытался восстановить seed.
Энтропия: почему это понятие часто не понимают?
Обычно энтропию объясняют как «меру хаоса». Представим колоду игральных карт, когда вы только купили колоду, вскрыв её, чувствуете запах, новая все карты лежат строго по порядку (низкая энтропия), но после перетасовки перед игрой карты идут в случайном порядке (высокая энтропия).
Аналогия полезная, но в контексте генерации паролей она немного обманывает.
Она создаёт ощущение, что энтропия — это свойство самого результата (пароля). На практике это не всегда так.
Энтропия — это свойство процесса генерации, а не строки.
Формально всё выглядит просто:

Если развернуть:

где:
L — длина пароля
N — размер алфавита
Быстрая оценка энтропии:
Например:
8 символов, только сточные буквы латиницы a–z (26 символов):

8 символов, полный ASCII-набор (~94 символа):

На бумаге всё выглядит убедительно: увеличили алфавит — выросла энтропия.
Но здесь есть ключевое допущение, которое почти всегда остаётся за кадром: каждый символ должен быть выбран независимо и равновероятно. И вот это — слабое место всей модели.
Изучая общую тему паролей, заметил типичную ошибку мышления большинства непосвященных: “Если пароль длинный и выглядит случайным — значит он надёжный.”
Это неверно.
Два пароля могут выглядеть одинаково:
f9A$kL2pQzX1
Но:
один получен через криптографически стойкий источник случайности
другой - через предсказуемый PRNG (его использует модуль
random)С точки зрения формулы энтропии — разницы нет.
С точки зрения атаки — разница принципиальная.
Иллюзия энтропии
Ключевой момент, который я для себя зафиксировал:
формальная энтропия может сильно отличаться от реальной.
Почему это происходит:
Формула считает пространство возможных паролей
Атака же направлена на процесс генерации
Если генератор:
детерминирован (если известен seed — известны все значения);
имеет ограниченное внутреннее состояние;
или использует предсказуемый seed;
то злоумышленник атакует не N в степени L, а: пространство состояний генератора.
И это на порядки меньше!
Именно в этот момент у меня возник главный вопрос, который стал основой всей статьи:
если пароль имеет “высокую энтропию” по формуле — почему его всё ещё можно предсказать?
К этому моменту мы уже должны понять, что такой пароль подобрать перебором вряд ли получится, но тогда каким образом подобные пароли уязвимы?
Часть 2. Почему random — это не про безопасность
После того как мы разобрались с тем, что энтропия сама по себе ничего не гарантирует, логичный следующий шаг — посмотреть, откуда вообще берётся эта “случайность” в Python.
В Python модуль random — это не “источник случайности”, а детерминированный генератор псевдослучайных чисел (PRNG).
По умолчанию он использует алгоритм Mersenne Twister.
Если кратко:
у генератора есть внутреннее состояние (~19937 бит)
из этого состояния вычисляется последовательность чисел
каждое следующее значение полностью определяется предыдущим
То есть: если известно внутреннее состояние — известна вся последовательность вперёд и назад.
Важный момент: откуда берётся seed?
Возьмём во внимание, что Python использует os.urandom для seed. Это значит, что seed задаётся источниками случайности операционной системы: аппаратные прерывания, движения мыши, нажатия клавиш и другие непредсказуемые события системы. (некоторые другие языки программирования используют системное время для генерации seed'а).
По итогу у нас получается, что в Python версии 3.9.18, которую использует GigaChat:
random.random() → использует Mersenne Twister → инициализируется seed’ом, полученным из
os.urandom
Где именно ломается безопасность
Ключевая уязвимость не в seed как таковом, а в свойствах самого генератора:
Детерминированность. Один seed → одна и та же последовательность
Восстановимость состояния. Для MT19937 известно, что его внутреннее состояние можно восстановить по наблюдаемым значениям
Отсутствие криптографической стойкости. Алгоритм не проектировался для защиты от атак
Это означает: даже если seed был получен из os.urandom, это не делает генератор криптографически безопасным!
Практическое следствие:
Если пароль генерируется так:
import random
import string
alphabet = string.ascii_letters + string.digits
def generate_password(length=12):
return ''.join(random.choice(alphabet) for _ in range(length))
то:
каждый символ зависит от состояния PRNG
вся строка — это просто “срез” внутренней последовательности
А значит, теоретически: восстановив состояние генератора, можно восстановить и все пароли.
Насколько это реально?
И тут я подумал - Если у меня будет какая-то минимальная информация, можно ли восстановить seed? (На момент, когда я задавался этим вопрос я думал, что в python, random генерирует seed исходя из времени в операционной системе).
Интуитивно кажется, что нет — ведь пространство большое.
Но:
если seed завязан на время (или его можно приблизить)
если генератор используется многократно
если есть несколько наблюдений
то задача резко упрощается.
Я как раз пробовал пойти этим путём в рамках исследования — и именно здесь начинаются интересные ограничения (о них позже).
Теперь контраст: secrets
На фоне random модуль secrets устроен принципиально иначе.
Он не реализует свой PRNG. Вместо этого он напрямую использует системный источник случайности (как минимум для каждого пароля, а как максимум для каждого символа одного пароля):
secrets.randbelow() → os.urandom()
random.SystemRandom() → тоже os.urandom()
Если коротко: здесь уже нет фиксированного внутреннего состояния, которое можно реконструировать.
И вот здесь становится понятна ключевая проблема: два внешне одинаковых пароля могут иметь принципиально разную безопасность — только из-за выбора генератора.
Почему это критично в реальности:
Когда GigaChat генерирует пароль через random. Пользователь, который «не в теме» видит:
длину
«случайность»
разнообразие символов
Но не понимает:
какой генератор использовался
можно ли воспроизвести последовательность
И это создаёт опасную иллюзию: пароль выглядит как криптостойкий, но им не является!
Часть 3. Попытка восстановления seed на практике
Возвращаясь к тому, что я говорил перед тем, как рассказать о модуле secrets.
Напомню, я задался вопросом - Если у меня будет какая-то минимальная информация, можно ли восстановить seed?
У меня были:
5 сгенерированных паролей GigaChat’ом обязательно подряд
известный код генерации
время и дата генерации 24.03.2026 11:11
версия Python 3.9.18
Интуитивно казалось: этого должно хватить, чтобы хотя бы сузить пространство поиска.
Первая гипотеза: seed = времени операционной системы сервера GigaChat'a
Это одновременно и очевидная идея, и самая простая в попытке "взлома" — предположить, что seed равен текущему времени (или близок к нему).
Это классическая ошибка, которую действительно иногда допускают в реальных системах.
Я написал перебор по окну ±1 час:
base_time = datetime(2026, 3, 24, 11, 11, 0)
window_seconds = 3600
for offset in range(-window_seconds, window_seconds):
t = base_time + timedelta(seconds=offset)
seed = int(t.timestamp())
Дальше - генерация и сравнение с целевыми паролями.
Логика простая:
если генератор инициализировался временем
и я попал в нужный интервал → я должен найти совпадение
Но результат: Не найдено
На этом этапе помимо того, что я расстроился, мне стало понятно: моя модель генерации не совпадает с реальной.
И тут я подумал - Может быть seed, вообще не равен времени?
А может, ты атаковал не тот уровень?
Это ключевой момент, который у меня щёлкнул не сразу.
Я пытался узнать: какой был seed?
Но в случае с Mersenne Twister правильная атака выглядит иначе: восстановить внутреннее состояние генератора по выходным данным
Для MT19937 это теоретически возможно, но есть нюанс: нужно ~624 последовательных значения генератора, причём в “сыром” виде (32-битных числах).
А у нас есть: уже преобразованные значения (choices), которые в свою очередь ещё и дополнительно “сжаты” до символов, так ещё и их всего 5 последовательных паролей.
То есть мы видим не сам выход PRNG, а его производную (сильно искажённый вывод).
А это означает, что обратное восстановление состояния становится крайне сложным, а в большинстве случаев — практически невозможным.
На что я надеялся, реализуя подобную атаку
А именно - на совпадение нескольких условий, причем обязательно всех одновременно:
Seed зависит от времени
Окно поиска достаточно узкое
Генерация воспроизводима 1-в-1
Используется random, а не SystemRandom
Исходя из чего имеем, что если хотя бы одно из условий нарушено — атака разваливается.
Итог практики
Этот эксперимент оказался полезен не тем, что “сломал генератор”, а тем, что чётко показал границы применимости атак.
Что стоит зафиксировать для себя:
random теоретически уязвим
но атака требует правильной модели и достаточных данных
попытка угадать seed через время — слишком наивная модель
использование os.urandom() полностью ломает этот подход
Финальный вывод этой статьи
Проблема random не в том, что его всегда можно взломать.
Проблема в том, что он не предназначен для безопасности и не даёт никаких гарантий.
Если генератор инициализируется предсказуемо — он становится уязвим.
Если нет — атака может просто не сработать.
И это худший сценарий для безопасности:
Именно поэтому в Python есть secrets и os.urandom() - не как «улучшенная версия random», а как инструменты с другой моделью угроз.
Проще запомнить, что:
random— для моделирования, игр и статистики.secrets— для всего, что связано с безопасностью.
И смешивать их — значит полагаться не на защиту, а на удачу.
