MAATRIX / Блог / Откуда сервер берёт случайные числа и что происходит, когда энтропия кончается

Откуда сервер берёт случайные числа и что происходит, когда энтропия кончается

MAATRIX

Каждый раз, когда сервер генерирует SSH-ключ, открывает TLS-сессию или выдаёт токен авторизации, где-то внутри ядра должно родиться число, которое никто не сможет угадать или воспроизвести. Если это число окажется не таким уж случайным, вся криптография сверху — хоть AES-256, хоть RSA-4096 — рушится: замок остаётся крепким, но ключ к нему можно подобрать. Разберёмся, откуда операционная система берёт настоящую случайность, почему миф про «сервер без мыши и клавиатуры не может её собрать» давно не работает, и что делать, если на вашей машине встречаются жалобы на нехватку энтропии.

Зачем вообще нужна криптографически стойкая случайность

Обычный rand() из стандартной библиотеки C или Math.random() в JavaScript — это генератор псевдослучайных чисел (PRNG) с предсказуемым внутренним состоянием. Зная несколько выданных им чисел и алгоритм, можно восстановить всю последовательность вперёд и назад. Для симуляций и игр это нормально. Для криптографии — катастрофа.

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

  • Генерация ключей. RSA- и Ed25519-ключи для SSH, ключи для TLS-сертификатов, симметричные ключи для дискового шифрования — все они начинаются со случайного числа. Если два сервера сгенерировали ключи из одинакового или предсказуемого состояния генератора, у них могут совпасть приватные ключи — и об этом сразу узнает любой, кто перебирает публичные ключи в интернете.
  • TLS-сессии. При каждом рукопожатии клиент и сервер обмениваются случайными значениями (ClientRandom/ServerRandom), из которых потом выводится сессионный ключ. Если это значение угадываемо, шифрование сессии можно взломать, даже не трогая сам сертификат — тему согласования ключей на этом этапе мы разбирали в статье про TLS-рукопожатие.
  • Токены и cookie сессий. Идентификатор сессии, CSRF-токен, API-ключ — всё это должно быть непредсказуемым числом достаточной длины. Предсказуемый токен позволяет злоумышленнику угадать чужую активную сессию, не взламывая пароль.
  • Соль для хешей и nonce. Соль в хеше пароля, nonce в AEAD-шифровании, IV для блочных шифров — все они обязаны быть уникальными и непредсказуемыми, иначе открываются атаки на повторное использование.

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

Откуда берётся настоящая непредсказуемость

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

  • Джиттер таймингов прерываний. Между приходом двух аппаратных прерываний (сетевая карта приняла пакет, диск закончил операцию, таймер сработал) проходит время, которое зависит от огромного числа факторов — состояния кеша процессора, загрузки шины, температурного дрейфа частоты. На уровне наносекунд эти колебания непредсказуемы даже для самого процессора.
  • Тайминги устройств ввода. Движения мыши и нажатия клавиш исторически были главным источником энтропии на настольных системах: момент нажатия клавиши человеком принципиально не подчиняется алгоритму.
  • Тайминги дисковых операций (для вращающихся HDD, где механика вносит физический шум) и сетевого трафика — время прихода пакетов извне тоже несёт в себе долю непредсказуемости, хотя и не такую сильную, как раньше, из-за высокой предсказуемости современных SSD и NIC на высокой скорости.
  • Аппаратный генератор случайных чисел в процессоре. Начиная с Ivy Bridge на стороне Intel и с аналогичных поколений AMD, в CPU встроен генератор на основе теплового шума транзисторов — инструкция RDRAND отдаёт готовое случайное число, RDSEED — более медленное, но предназначенное именно для затравки других генераторов. У серверных ARM-процессоров есть похожие механизмы через расширение FEAT_RNG.

Ядро не доверяет ни одному источнику по отдельности — оно смешивает их все через криптографическую хеш-функцию в единый пул энтропии. Даже если один источник окажется предсказуемым (скажем, злоумышленник как-то контролирует сетевой трафик), остальные источники не дадут результату стать угадываемым: вклад в энтропию считается по принципу «хуже не станет», а не «худший источник всё портит».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Пул энтропии, /dev/urandom и разница с /dev/random

Классическая модель Linux, которая живёт с начала 1990-х, — это два устройства. /dev/random исторически блокировал чтение, если ядро считало, что «накопленной энтропии» не хватает. /dev/urandom никогда не блокировал: после первой инициализации отдавал криптографически стойкий поток на основе внутреннего CSPRNG — генератора, детерминированного по алгоритму, но непредсказуемого без знания секретного состояния.

Разница вводила в заблуждение десятилетиями: разработчики решали, что /dev/random «безопаснее», потому что «более случайный», и городили костыли, ждущие энтропию по десять секунд на старте сервиса. На деле после того как внутренний генератор один раз получил достаточно непредсказуемое стартовое состояние (сид), его вывод неотличим от истинной случайности для любого практически достижимого вычислительного ресурса — досыпать в него ещё энтропии не делает следующий байт менее предсказуемым. Внутри ядро держит собственный CSPRNG (в современных версиях Linux — на основе ChaCha20), а не «резервуар», из которого случайность физически «вычерпывается» и «кончается» в привычном бытовом смысле: каждое событие добавляет свой тайминг в пул через операцию перемешивания, ядро консервативно оценивает вклад в битах непредсказуемости, а как только накопленная оценка переходит порог, генератор считается проинициализированным (seeded) и способен детерминированно и безопасно растягивать ограниченное секретное состояние в неограниченный поток, неотличимый от случайного — пока само состояние не скомпрометировано.

Разработчики ядра признали путаницу с двумя устройствами и начиная с Linux 5.6 сделали /dev/random и /dev/urandom эквивалентными после того, как генератор один раз проинициализирован: оба не блокируются в обычной работе, а блокирующее поведение осталось только на самой ранней стадии загрузки, пока пул не набрал минимально достаточное состояние. Современный рекомендуемый способ получить случайность из пользовательского пространства — не читать файл напрямую, а вызывать системный вызов getrandom(), который сам разбирается, готов ли генератор. Библиотеки вроде OpenSSL и большинство языковых рантаймов (Python secrets, Go crypto/rand, Node crypto.randomBytes) под капотом идут именно через него.

Отсюда следует важная вещь: показатель /proc/sys/kernel/random/entropy_avail, который многие годами проверяли скриптами мониторинга, — это оценка запаса в старой модели пула, а не индикатор того, что криптография вот-вот сломается. После однократной инициализации системы он почти не влияет на безопасность практических приложений — это метрика, унаследованная от домодульной архитектуры, и на некоторых современных ядрах она вообще держится на фиксированном максимуме.

Миф «на сервере без мыши не хватает энтропии» и как его закрыли

Миф родился в реальной проблеме прошлого десятилетия: голый сервер без монитора, мыши и клавиатуры, на раннем этапе загрузки не успевал накопить достаточно физического шума. Хуже всего дело обстояло со свежесозданными виртуальными машинами: гипервизор мог поднять десятки клонов из одного образа почти одновременно, и если генератор каждого клона стартовал с идентичного состояния, ключи могли получаться похожими или даже совпадающими. Отсюда реальные исторические инциденты с предсказуемыми SSH- и TLS-ключами на массово клонированных облачных образах.

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

  • Аппаратный RNG процессора. RDRAND/RDSEED (или их аналог на ARM) дают ядру источник шума, который не зависит от периферии — он есть даже на «безголовом» сервере в стойке дата-центра.
  • virtio-rng в виртуальных машинах. Гипервизор пробрасывает гостю виртуальное RNG-устройство, которое берёт энтропию у хост-системы. Это снимает главную боль клонированных VM: каждый клон получает независимый вклад от хоста, а не только от собственного (одинакового у всех клонов) состояния при старте.
  • Сохранение сида между перезагрузками. systemd-random-seed при выключении сохраняет часть состояния генератора в файл на диске и подмешивает его при следующей загрузке — система не начинает «с нуля» каждый раз.
  • Раннее засеивание при сборке образа. Дистрибутивы стараются не запекать один и тот же сид в шаблон образа: свежий сид досеивается при первой загрузке конкретного инстанса, а не наследуется от родителя как есть.

В сумме на подавляющем большинстве современных серверов — физических или VPS на актуальном гипервизоре — генератор ядра успевает проинициализироваться за доли секунды после старта, и жалобы «недостаточно энтропии» из системных логов десятилетней давности сегодня в норме не появляются. Если вы всё же видите их в dmesg, это чаще сигнал о нетипичном окружении (урезанный контейнер без доступа к RNG-устройству хоста, старое ядро), а не норма для обычной аренды сервера.

Что реально идёт не так, если случайность слабая

Стоит разделить два разных сценария, которые на бытовом уровне путают под одной фразой «не хватило энтропии»:

  • Генератор ещё не проинициализирован при первом обращении. На самой ранней стадии загрузки вызов, требующий гарантированно стойкой случайности, может ненадолго задержаться — это штатное поведение защиты, а не сбой. Проблема возникает, когда сервис написан так, что вместо ожидания подставляет более слабый источник «лишь бы не блокироваться».
  • Состояние генератора предсказуемо не из-за нехватки энтропии, а из-за ошибки реализации. Самый известный пример в истории Linux — баг в пакете OpenSSL для Debian и Ubuntu в 2006–2008 годах, когда патч дистрибутива случайно вырезал код, отвечающий за подмешивание энтропии, и генератор массово стартовал с предсказуемого состояния, зависящего только от PID процесса. Это не проблема нехватки физической энтропии — это software-баг, который обнулил уже написанный правильный механизм. Даже идеально спроектированный источник случайности бесполезен, если между ним и приложением есть ошибка интеграции.

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

Как проверить состояние генератора на своём сервере

Несколько практических команд, которые стоит знать, если вы администрируете VPS или выделенный сервер:

Проверить, инициализирован ли генератор ядра (для ядер, где это событие логируется):

dmesg | grep -i random
journalctl -k | grep -i "crng init"

Оценка запаса энтропии в старой модели (полезна как индикатор для очень старых ядер или урезанных контейнеров, не как показатель реальной стойкости на современном ядре):

cat /proc/sys/kernel/random/entropy_avail

Проверить, доступен ли аппаратный RNG-источник и какой драйвер его обслуживает:

cat /sys/devices/virtual/misc/hw_random/rng_current
lsmod | grep -i rng

Внутри виртуальной машины проверить наличие virtio-rng:

lspci | grep -i virtio
ls -l /dev/hwrng 2>/dev/null

Получить криптографически стойкие случайные байты штатными средствами (для тестов, генерации токенов вручную и т. п.):

openssl rand -hex 32
head -c 32 /dev/urandom | xxd -p

Если вы всё же управляете старым окружением без аппаратного RNG и видите задержки при старте сервисов, rngd из пакета rng-tools — стандартный демон, подкачивающий энтропию из доступных программных источников; на большинстве современных VPS он не требуется, так как аппаратный источник и virtio-rng уже закрывают потребность.

Отдельный практический момент для тех, кто разворачивает сервисы через готовые образы: если вы клонируете диск целиком, а не поднимаете инстанс штатным механизмом гипервизора, убедитесь, что файл сохранённого сида (/var/lib/systemd/random-seed или аналог) не скопирован один в один во все клоны без пересеивания — иначе вы своими руками воссоздаёте ту самую проблему одинаковых стартовых состояний. При штатном разворачивании VPS через панель провайдера гипервизор сам обеспечивает независимый вклад энтропии каждому инстансу через virtio-rng, и вручную ничего чинить не нужно.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Нужно ли устанавливать haveged на современный сервер, чтобы "добавить энтропии"?

Как правило нет. haveged был актуален во времена, когда ядро долго блокировалось на старте без аппаратного RNG; на актуальных ядрах с поддержкой RDRAND/RDSEED и virtio-rng в облаке он обычно не нужен и лишь добавляет ещё один процесс для поддержки. Ставьте его только если диагностировали реальную задержку инициализации в конкретном урезанном окружении.

Чем /dev/urandom хуже /dev/random для генерации ключей?

После однократной инициализации системы — практически ничем: оба возвращают вывод одного и того же криптографически стойкого генератора. Разница исторически была в поведении на раннем старте до инициализации; на современных ядрах (начиная с Linux 5.6) устройства ведут себя эквивалентно в обычной работе.

Как узнать, что процесс генерации SSH-ключей на моём сервере не использовал предсказуемое состояние?

Прямого способа проверить постфактум задним числом нет, но можно снизить риск: генерировать ключи на системе с современным ядром и обновлёнными пакетами, не переиспользовать образ диска с уже присутствующим состоянием генератора между несвязанными инстансами и, для критичных ключей, сверять отпечаток (ssh-keygen -lf key.pub) со списками известных скомпрометированных ключей (например, по мотивам debian-openssl-инцидента такие списки существуют публично).

Отличается ли получение случайности в контейнере (Docker) от обычного сервера?

Контейнер обычно использует ядро хоста напрямую, поэтому получает случайность из того же уже проинициализированного генератора хоста через getrandom() — отдельного «пула» у контейнера нет. Проблемы возникают в основном в сильно урезанных или изолированных средах, где системный вызов ограничен политикой seccomp — тогда стоит явно проверить, что getrandom() доступен внутри контейнера.

Стоит ли беспокоиться об энтропии при генерации TLS-сертификата на новом VPS сразу после установки?

На актуальном дистрибутиве и современном гипервизоре — нет: аппаратный RNG и virtio-rng инициализируют генератор ядра за доли секунды после старта, задолго до того, как вы вручную дойдёте до команды openssl req или certbot. Если хотите перестраховаться, просто подождите минуту после первой загрузки перед генерацией ключей — этого более чем достаточно на практике.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →