Случайные числа в Linux(RNG) или как «наполнить» /dev/random и /dev/urandom

Пожалуй всем пользователям Linux  известны такие файлы псевдо-устройств как /dev/random и /dev/urandom. Эти устройства являются интерфейсом к генератору случайных чисел ядра(RNG, random number generator).

Случайные числа(они же — непредсказуемый набор битов) очень важны в криптографии. Они являются базовыми кирпичиками для криптографических протоколов. От качества (неповторимости, непредсказуемости) этих чисел зависит стойкость всевозможных TLS-сертификатов, SSH и GPG ключей, сессионных симметричных TLS-ключей и т.д. Так же случайные числа являются основой для генерации UUID’ов, PID’ов, TCP sequence numbers и многого другого.

RNG генерит случайные числа  на основе данных из пула энтропии(entropy pool) в ядре Linux. Наполнением этого пула так же занимается RNG и делается это на основе случайных событий в системе таких как: тайминги клавиатуры и дисков, движения мыши, прерывания(interrupts), сетевой трафик.

Пул энтропии имеет фиксированный объем 4096 bits:

cat /proc/sys/kernel/random/poolsize
4096

Размер пула нельзя изменить, это захардкожено в ядре.

Посмотреть текущий объем данных в пуле:

cat /proc/sys/kernel/random/entropy_avail
186

Доступный объем энтропии постоянно меняется, в зависимости от скорости пополнения и потребления соответственно.

Собственно через /dev/random и /dev/urandom приложения в user space получают эти самые случайные числа\данные.

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

/dev/urandom  — unlimited random, не блокирующийся и приложения могут читать из него бесконечно. Предоставляет случайные данные такого же высокого качества что и /dev/random но до тех пор пока пул энтропии не опустеет. Когда пул будет пустым, /dev/urandom продолжит выдавать случайные данные но теоретически сильно меньшего качества.

Настоятельно рекомендуется для любых долго-живущих ключей, например для TLS-сертификатов использовать /dev/random т.к. только он гарантирует качество случайных чисел. Но, большинство приложений таких как Apache, Nginx, sshd и о Боже ssh-keygen, openssl используют /dev/urandom. Тут в принципе понятно Apache, Nginx, sshd не хотят блокироваться при генерации сессионных ключей. Но то, что ssh-keygen и openssl используют по умолчанию /dev/urandom меня поразило. Причем для openssl можно задать устройство при генерации ключей(ниже пример) а вот для ssh-keygen я возможности переопределить поведение не нашел.

В общем не важно откуда читают ваши приложения, нельзя допускать опустошение этого самого пула энтропии и тогда счастье будет и с /dev/urandom.

Прежде чем начать “майнить” энтропию, пара слов о ее корректном использовании из man /dev/random:

The amount of seed material required to generate a cryptographic key equals the effective key size of the key. For example, a 3072-bit RSA or Diffie-Hellman private key has an effective key size of 128 bits (it requires about 2^128 operations to break) so a key generator only needs 128 bits (16 bytes) of seed material from /dev/random.

Например openssl для генерации RSA ключа длиной 10240 bit использует всего 2048 исходного материала из /dev/random:

strace -e open openssl genrsa -rand /dev/random -out rootCA.key 10240
….
open("/dev/random", O_RDONLY)           = 4
2048 semi-random bytes loaded
Generating RSA private key, 10240 bit long modulus

Как заполнить пул энтропии?

Лучшее решение это использование специальных аппаратных средств(TPM, Trusted Platform Module) или инструкций процессора типа RDRAND(есть в Intel IvyBridge и Haswell процессорах).

Проверить наличие подобных устройств на сервере поможет утилита rngd из пакета rng-tools

rngd -v
Unable to open file: /dev/tpm0
can't open any entropy source
Maybe RNG device modules are not loaded

Если rngd обнаружит поддерживаемые средства то вам повезло и вы можете запустить сервис rngd. В моем случае их нет)

Собственно задача rngd читать энтропию из аппаратных средств и наполнять ей пул энтропии ядра. Делается это через специальный ioctl вызов(RNDADDENTROPY) интерфейса /dev/random.

Если нет аппаратной поддержки

В интернете можно встретить рекомендации, где предлагают указывать /dev/urandom как источник энтропии для rngd. То есть источником энтропии для ядра по сути будет само ядро). Это довольно сомнительная идея, и я бы не стал так делать и вам не советую. Но ради эксперимента я провел тесты и результаты(которые ниже) тоже довольно не плохие.

Havegd

В основе лежит алгоритм HAVAGE который генерирует энтропию на основе счётчиков и состояний процессора. В силу сложного, многоуровневого устройства процессоров, один и тот же код всегда выполняется за разное время и это не постоянство является основой для алгоритма HAVAGE.

На практике, это user space демон который как и rngd наполняет пул энтропии ядра через ioctl интерфейс /dev/random. при этом не нуждается в источнике энтропии как rngd.

Для centos пакет доступен из epel.

После установки нужно просто запустить сервис. С параметрами по умолчанию haveged будет стараться держать пул энтропии ядра на уровне не ниже 1024.

Тестирование

Без rngd и haveged, команда(ниже будет понятно, что она делает):

cat /dev/random | rngtest -c 10000

Не завершилась за сутки!

rngd с /dev/urandom в качестве источника энтропии

(то, что я вам не рекомендовал)

Unit-файл rngd.service:

[Unit]
Description=Hardware RNG Entropy Gatherer Daemon
[Service]
ExecStart=/sbin/rngd -f -r /dev/urandom -o /dev/random
[Install]
WantedBy=multi-user.target

Тест(худший из 3-х результат):

cat /dev/random | rngtest -c 10000

rngtest 5
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
rngtest: starting FIPS tests...
rngtest: bits received from input: 200000032
rngtest: FIPS 140-2 successes: 9989
rngtest: FIPS 140-2 failures: 11
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 2
rngtest: FIPS 140-2(2001-10-10) Runs: 6
rngtest: FIPS 140-2(2001-10-10) Long run: 3
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=3.234; avg=7.202; max=7.981)Mibits/s
rngtest: FIPS tests speed: (min=90.826; avg=142.151; max=146.719)Mibits/s
rngtest: Program run time: 27833279 microseconds

Тут нужно смотреть на successes, failures, и на input channel speed.

При этом утилизация CPU процессом rngd: 57%

haveged

Тест(худший из 3-х результат):

cat /dev/random | rngtest -c 10000

rngtest 5
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
rngtest: starting FIPS tests...
rngtest: bits received from input: 200000032
rngtest: FIPS 140-2 successes: 9993
rngtest: FIPS 140-2 failures: 7
rngtest: FIPS 140-2(2001-10-10) Monobit: 1
rngtest: FIPS 140-2(2001-10-10) Poker: 0
rngtest: FIPS 140-2(2001-10-10) Runs: 4
rngtest: FIPS 140-2(2001-10-10) Long run: 2
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=1.780; avg=15.692; max=18.755)Mibits/s
rngtest: FIPS tests speed: (min=79.473; avg=143.212; max=150.185)Mibits/s
rngtest: Program run time: 13493892 microseconds

При этом утилизация CPU процессом haveged:12%

Виртуальные машины

Не рекомендуется использовать haveged внутри ВМ, т.к. там  вроде как счетчики CPU не такие точные(типа округляются) и это сказывается на качестве энтропии.
Тру путь это использовать virt-ioRNG(qemu-kvm) — паравиртуальное устройство которое будет брать энтропию из пула хоста. Но, это уже совсем другая история…)