Кажется, я вижу много ответов, в которых кто-то предлагает использовать <random>
для генерации случайных чисел, обычно вместе с таким кодом:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
Обычно это заменяет какую-то «нечестивую мерзость» типа:
srand(time(NULL));
rand()%6;
Мы могли бы критиковать старый способ, утверждая, что он time(NULL)
обеспечивает низкую энтропию, time(NULL)
предсказуемость и конечный результат неоднороден.
Но все это верно в отношении нового способа: у него просто более блестящий внешний вид.
rd()
возвращает синглunsigned int
. Он имеет как минимум 16 бит, а возможно, 32. Этого недостаточно для заполнения 19937 бит состояния MT.Использование
std::mt19937 gen(rd());gen()
(заполнение 32 битами и просмотр первого вывода) не дает хорошего распределения вывода. 7 и 13 никогда не могут быть первым выходом. Два семени дают 0. Двенадцать семян дают 1226181350. ( Ссылка )std::random_device
может быть, а иногда и реализуется как простой ГПСЧ с фиксированным начальным значением. Следовательно, при каждом запуске может производиться одна и та же последовательность. ( Ссылка ) Это даже хуже чемtime(NULL)
.
Что еще хуже, очень легко скопировать и вставить приведенные выше фрагменты кода, несмотря на проблемы, которые они содержат. Некоторые решения этой проблемы требуют приобретения обширных библиотек, которые могут не подходить всем.
В свете этого, мой вопрос: как можно кратко, портативно и тщательно засеять mt19937 PRNG в C ++?
Учитывая вышеперечисленные проблемы, хороший ответ:
- Необходимо полностью засеять mt19937 / mt19937_64.
- Нельзя полагаться только на источник энтропии
std::random_device
илиtime(NULL)
как на ее источник. - Не следует полагаться на Boost или другие библиотеки.
- Должен уместиться в небольшом количестве строк, чтобы он выглядел хорошо вставленным в ответ.
мысли
Моя текущая мысль заключается в том, что выходные данные
std::random_device
можно объединить (возможно, с помощью XOR) соtime(NULL)
значениями, полученными в результате рандомизации адресного пространства , и жестко запрограммированной константой (которая может быть установлена во время распределения), чтобы получить максимальную отдачу от энтропии.std::random_device::entropy()
не дает четкого представления о том, чтоstd::random_device
может или не может сделать.
std::random_device
,time(NULL)
и адреса функций, затем выполняется XOR вместе , чтобы произвести своего рода источник энтропии максимальных усилий.std::random_device
правильной реализации на платформах, на которых вы планируете запускать свою программу, и предоставлении вспомогательной функции, которая создает генератор с засеяннымиseed11::make_seeded<std::mt19937>()
Ответы:
Я бы сказал, что самый большой недостаток
std::random_device
заключается в том, что разрешен детерминированный откат, если CSPRNG недоступен. Уже одно это является хорошей причиной не заполнять ГПСЧ с помощьюstd::random_device
, поскольку производимые байты могут быть детерминированными. К сожалению, он не предоставляет API, чтобы узнать, когда это происходит, или запросить сбой вместо случайных чисел низкого качества.То есть полностью переносимого решения нет: зато есть достойный, минималистичный подход. Вы можете использовать минимальную оболочку вокруг CSPRNG (определенного
sysrandom
ниже) для заполнения PRNG.Windows
Вы можете положиться на
CryptGenRandom
CSPRNG. Например, вы можете использовать следующий код:Unix-подобный
Во многих Unix-подобных системах по возможности следует использовать / dev / urandom (хотя это не гарантируется в POSIX-совместимых системах).
Другой
Если CSPRNG недоступен, вы можете полагаться на него
std::random_device
. Однако я бы по возможности избегал этого, поскольку различные компиляторы (в первую очередь, MinGW) реализуют его как ГПСЧ (фактически, каждый раз создавая одну и ту же последовательность, чтобы предупредить людей о том, что она не является должным образом случайной).Посев
Теперь, когда у нас есть части с минимальными накладными расходами, мы можем сгенерировать желаемые биты случайной энтропии для заполнения нашего ГПСЧ. В примере используются (явно недостаточные) 32 бита для заполнения PRNG, и вам следует увеличить это значение (которое зависит от вашего CSPRNG).
Сравнение с Boost
Мы можем увидеть параллели с boost :: random_device (настоящий CSPRNG) после беглого просмотра исходного кода . Boost используется
MS_DEF_PROV
в Windows, это тип поставщика дляPROV_RSA_FULL
. Единственное, чего не хватает, - это проверки криптографического контекста, что можно сделать с помощьюCRYPT_VERIFYCONTEXT
. На * Nix Boost использует/dev/urandom
. IE, это решение портативно, хорошо протестировано и простое в использовании.Специализация Linux
Если вы готовы пожертвовать краткостью ради безопасности,
getrandom
это отличный выбор для Linux 3.17 и новее, а также для недавней версии Solaris.getrandom
ведет себя идентично/dev/urandom
, за исключением того, что блокируется, если ядро еще не инициализировало свой CSPRNG после загрузки. Следующий фрагмент кода определяетgetrandom
, доступен ли Linux , и, если нет, возвращается к/dev/urandom
.OpenBSD
Есть еще одно последнее предостережение: в современной OpenBSD его нет
/dev/urandom
. Вместо этого вы должны использовать getentropy .другие мысли
Если вам нужны криптографически безопасные случайные байты, вам, вероятно, следует заменить fstream на небуферизованные open / read / close POSIX. Это потому , что , как
basic_filebuf
иFILE
содержит внутренний буфер, который будет выделен с помощью стандартного распределителя (и , следовательно , не стер из памяти).Это легко сделать, изменив
sysrandom
на:Спасибо
Особая благодарность Бену Фойгту за указание на
FILE
использование буферизованного чтения, поэтому его не следует использовать.Я также хотел бы поблагодарить Питера Кордеса за упоминание
getrandom
и отсутствие в OpenBSD/dev/urandom
.источник
/dev/random
что это будет лучший выбор для заполнения ГСЧ, но, по-видимому,/dev/urandom
он по-прежнему считается безопасным с точки зрения вычислений, даже когда/dev/random
блокируется из-за низкой доступной энтропии, поэтомуurandom
это рекомендуемый выбор для всего, кроме, возможно, одноразовых прокладок. См. Также unix.stackexchange.com/questions/324209/… . Однако остерегайтесь предсказуемых семян сurandom
самого начала после загрузки.getrandom(2)
Системный вызов Linux похож на открытие и чтение/dev/urandom
, за исключением того, что он блокируется, если источники случайности ядра еще не инициализированы. Я думаю, это избавит вас от проблемы ранней загрузки с низким качеством случайности без блокировки в других случаях, как это/dev/random
происходит./dev/urandom
обычно работает. Я обычно подписываюсь на обсуждение этого вопроса в списке рассылки Python: bugs.python.org/issue27266В некотором смысле это невозможно сделать портативно. Таким образом, можно представить себе действительную полностью детерминированную платформу, работающую на C ++ (скажем, симулятор, который детерминированно изменяет синхронизацию машинных часов и с «детерминированным» вводом-выводом), в которой нет источника случайности для заполнения ГПСЧ.
источник
std::random_device
это будет в этой категории, но, очевидно, это не так, если в некоторых реальных реализациях используется ГПСЧ с фиксированным начальным числом! Это выходит далеко за рамки аргументов Эйнпоклума.Вы можете использовать a
std::seed_seq
и заполнить его, по крайней мере, до требуемого размера состояния для генератора, используя метод Александра Хусзага для получения энтропии:Если бы существовал правильный способ заполнения или создания SeedSequence из UniformRandomBitGenerator в стандартной библиотеке, использование
std::random_device
для правильного заполнения было бы намного проще.источник
Реализация, над которой я работаю, использует
state_size
свойствоmt19937
PRNG, чтобы решить, сколько семян предоставить при инициализации:Я думаю, что есть возможности для улучшения, потому что они
std::random_device::result_type
могут отличатьсяstd::mt19937::result_type
по размеру и диапазону, поэтому это действительно следует учитывать.Замечание о std :: random_device .
Согласно
C++11(/14/17)
стандарту (-ам):Это означает, что реализация может генерировать детерминированные значения только в том случае, если ей не позволяют генерировать недетерминированные значения из - за некоторых ограничений.
MinGW
Компилятор наWindows
лихе не обеспечивает недетерминированное значение своегоstd::random_device
, несмотря на их быть легко доступны из операционной системы. Поэтому я считаю это ошибкой, которая вряд ли часто встречается в разных реализациях и на разных платформах.источник
std::random_device
и, следовательно, уязвимо для связанных с ним проблем.std::random_device
? Я знаю, что стандарт допускает такойPRNG
же откат, но я чувствую, что это только для прикрытия, поскольку трудно требовать, чтобы каждое используемое устройствоC++
имело недетерминированный случайный источник. А если они этого не сделают, что вы можете с этим поделать?std::random_device
. Я считаю, что это дух стандарта. Итак, я искал и могу найти толькоMinGW
то, что сломано в этом отношении. Кажется, никто не сообщает об этой проблеме ни с чем другим, что я нашел. Итак, в моей библиотеке я просто помеченMinGW
как неподдерживаемый. Если бы была более широкая проблема, я бы ее переосмыслил. Я просто не вижу доказательств этого прямо сейчас.std::random_device
всех, делая его доступным в форме, не обеспечивающей возможности случайности платформы. Низкокачественные реализации противоречат цели существующего API. Было бы лучше, ИМО, если бы они просто не реализовали его, пока он не заработал. (Или лучше, если бы API предоставлял способ запроса сбоя, если высококачественная случайность недоступна, поэтому MinGW мог бы избежать угроз безопасности, при этом давая разные начальные значения для игр или чего-то еще.)Нет ничего плохого в раздаче с использованием времени, если это вам не нужно для обеспечения безопасности (и вы не говорили, что это было необходимо). Понимание состоит в том, что вы можете использовать хеширование, чтобы исправить неслучайность. Я обнаружил, что это работает адекватно во всех случаях, включая, в частности, тяжелое моделирование методом Монте-Карло.
Одной из приятных особенностей этого подхода является то, что он обобщается на инициализацию из других не совсем случайных наборов начальных чисел. Например, если вы хотите, чтобы каждый поток имел свой собственный RNG (для обеспечения безопасности потоков), вы можете просто инициализировать его на основе хешированного идентификатора потока.
Ниже приведен SSCCE , полученный из моей кодовой базы (для простоты; некоторые структуры поддержки OO опущены):
источник
1
и2
и обратите внимание, что последовательность сгенерированных ими чисел с плавающей запятой требует времени, чтобы действительно расходиться).Вот мой собственный ответ на вопрос:
Идея здесь состоит в том, чтобы использовать XOR для объединения многих потенциальных источников энтропии (быстрое время, медленное время
std::random-device
, местоположения статических переменных, местоположения кучи, местоположения функций, местоположения библиотек, значения, специфичные для программы), чтобы предпринять максимальную попытку инициализировать mt19937. Если хотя бы один раз источник будет «хорошим», результат будет не менее «хорошим».Этот ответ не такой короткий, как хотелось бы, и может содержать одну или несколько логических ошибок. Так что я считаю это незавершенным. Прокомментируйте, если у вас есть отзыв.
источник
&i ^ &myseed
что энтропия должна быть значительно меньше, чем у любого из них по отдельности, поскольку оба являются объектами со статической продолжительностью хранения в одной и той же единице перевода и, следовательно, могут быть довольно близко друг к другу. И вы, кажется, на самом деле не используете специальное значение из инициализацииmyseed
?^
ужасный сумматор хешей; если два значения имеют много энтропии, но мало по сравнению друг с другом, она удаляется.+
обычно лучше (поскольку x + x сжигает только 1 бит энтропии в x, а x ^ x сжигает их все). Я подозреваю, что функция небезопасна (rd()
)+
я имею в виду на неподписанном (+
на подписанном - это UB-приманка). Хотя это несколько нелепые случаи UB, вы сказали портативные. Также рассмотрите возможность получения адреса функции в виде целого значения, если возможно (не уверены, есть ли это?)/dev/urandom
или/dev/random
).Они доступны в современных UNIX-подобных системах, таких как Linux, Solaris и OpenBSD.
источник
Данная платформа может иметь источник энтропии, например
/dev/random
. Наносекунды с начала эпохи,std::chrono::high_resolution_clock::now()
вероятно, являются лучшим начальным значением в стандартной библиотеке.Раньше я использовал что-то вроде того,
(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )
чтобы получить больше бит энтропии для приложений, которые не критичны с точки зрения безопасности.источник
/dev/urandom
, особенно в таком случае./dev/random
блоков, и часто без веских причин для этого ([вставьте длинное объяснение о том, сколько разных ОС оценивают случайность байтов, созданных / dev / random])./dev/urandom
не было, и альтернативой блокировке был детерминизм. Коробка может иметь/dev/hwrng
или/dev/hw_random
тоже, что должно быть даже лучше./dev/random
», и это, похоже, вызвало священную войну/dev/random
против/dev/urandom
Linux, чего я не имел в