Wydaje mi się, że widzę wiele odpowiedzi, w których ktoś sugeruje użycie <random>
do generowania liczb losowych, zwykle wraz z takim kodem:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
Zwykle zastępuje to jakąś „przeklętą obrzydliwość”, taką jak:
srand(time(NULL));
rand()%6;
Możemy krytykować stary sposób, argumentując, że time(NULL)
zapewnia niską entropię, time(NULL)
jest przewidywalny, a wynik końcowy jest niejednolity.
Ale to wszystko jest prawdą w przypadku nowego sposobu: ma po prostu bardziej błyszczącą okleinę.
rd()
zwraca pojedynczyunsigned int
. Ma to co najmniej 16 bitów i prawdopodobnie 32. To nie wystarczy, aby zaszczepić 19937 bitów stanu MT.Używanie
std::mt19937 gen(rd());gen()
(zapełnianie 32 bitami i patrzenie na pierwsze wyjście) nie daje dobrego rozkładu wyników. 7 i 13 nigdy nie mogą być pierwszym wyjściem. Dwa nasiona dają 0. Dwanaście nasion daje 1226181350. ( Link )std::random_device
może być, a czasami jest, zaimplementowany jako prosty PRNG ze stałym nasieniem. Może zatem tworzyć tę samą sekwencję w każdym przebiegu. ( Link ) To jest jeszcze gorsze niżtime(NULL)
.
Co gorsza, bardzo łatwo jest skopiować i wkleić powyższe fragmenty kodu, pomimo problemów, które zawierają. Niektóre rozwiązania tego problemu wymagają pozyskania dużych bibliotek, które mogą nie być odpowiednie dla wszystkich.
W związku z tym moje pytanie brzmi: Jak zwięźle, przenośnie i dogłębnie posiać mt19937 PRNG w C ++?
Biorąc pod uwagę powyższe kwestie, dobra odpowiedź:
- Musi w pełni obsiać mt19937 / mt19937_64.
- Nie można polegać wyłącznie na
std::random_device
lubtime(NULL)
jako źródle entropii. - Nie powinien polegać na Boost lub innych bibliotekach.
- Powinien zmieścić się w niewielkiej liczbie wierszy, tak aby ładnie wyglądał po wklejeniu do odpowiedzi.
Myśli
Obecnie
std::random_device
sądzę, że dane wyjściowe from mogą zostać zmiksowane (być może przez XOR) ztime(NULL)
wartościami pochodzącymi z randomizacji przestrzeni adresowej i stałą zakodowaną na stałe (którą można ustawić podczas dystrybucji), aby uzyskać najlepszy możliwy strzał w entropię.std::random_device::entropy()
nie daje dobrego wskazania, costd::random_device
może, a czego nie.
std::random_device
,time(NULL)
i adresy funkcję, a następnie XORed razem w celu wytworzenia rodzaju best-effort źródła entropii.std::random_device
poprawnym wdrożeniu na platformach, na których planujesz uruchomić swój program, i zapewnieniu funkcji pomocniczej, która tworzy generator z nasionami (seed11::make_seeded<std::mt19937>()
)Odpowiedzi:
Twierdzę, że największą wadą
std::random_device
jest to, że dozwolone jest deterministyczne rozwiązanie awaryjne, jeśli nie jest dostępny CSPRNG. Już samo to jest dobrym powodem, aby nie wysiewać PRNG przy użyciustd::random_device
, ponieważ produkowane bajty mogą być deterministyczne. Niestety nie zapewnia interfejsu API, aby dowiedzieć się, kiedy tak się stanie, lub zażądać błędu zamiast niskiej jakości liczb losowych.Oznacza to, że nie ma całkowicie przenośnego rozwiązania: istnieje jednak przyzwoite, minimalne podejście. Możesz użyć minimalnego opakowania wokół CSPRNG (zdefiniowanego
sysrandom
poniżej), aby zainicjować PRNG.Windows
Możesz polegać na
CryptGenRandom
CSPRNG. Na przykład możesz użyć następującego kodu:Podobne do systemu Unix
W wielu systemach uniksopodobnych powinieneś używać / dev / urandom, jeśli to możliwe (chociaż nie ma gwarancji, że będzie istnieć w systemach zgodnych z POSIX).
Inny
Jeśli nie ma dostępnego CSPRNG, możesz polegać
std::random_device
. Jednak unikałbym tego, jeśli to możliwe, ponieważ różne kompilatory (w szczególności MinGW) implementują go jako PRNG (w rzeczywistości produkują tę samą sekwencję za każdym razem, aby ostrzegać ludzi, że nie jest ona właściwie losowa).Wysiew
Teraz, gdy mamy już nasze elementy z minimalnym narzutem, możemy wygenerować pożądane bity losowej entropii, aby zasiać nasz PRNG. W przykładzie użyto (oczywiście niewystarczających) 32-bitowych do zapoczątkowania PRNG i powinieneś zwiększyć tę wartość (która jest zależna od twojego CSPRNG).
Porównanie do wzmocnienia
Widzimy podobieństwa do boost :: random_device (prawdziwy CSPRNG) po szybkim spojrzeniu na kod źródłowy . Boost używa
MS_DEF_PROV
w systemie Windows, który jest typem dostawcy dlaPROV_RSA_FULL
. Jedyne, czego brakowało, to zweryfikowanie kontekstu kryptograficznego, z którym można to zrobićCRYPT_VERIFYCONTEXT
. W * Nix Boost używa/dev/urandom
. IE, to rozwiązanie jest przenośne, dobrze przetestowane i łatwe w użyciu.Specjalizacja w Linuksie
Jeśli chcesz poświęcić zwięzłość dla bezpieczeństwa,
getrandom
jest to doskonały wybór na Linuksie 3.17 i nowszych oraz na najnowszym Solarisie.getrandom
zachowuje się identycznie/dev/urandom
, z wyjątkiem tego, że blokuje się, jeśli jądro nie zainicjowało swojego CSPRNG jeszcze po uruchomieniu. Poniższy fragment kodu wykrywa, czy Linuxgetrandom
jest dostępny, a jeśli nie, wraca do/dev/urandom
.OpenBSD
Jest jeszcze jedno ostatnie zastrzeżenie: współczesne OpenBSD nie ma
/dev/urandom
. Zamiast tego powinieneś użyć getentropy .inne przemyślenia
Jeśli potrzebujesz zabezpieczonych kryptograficznie losowych bajtów, prawdopodobnie powinieneś zamienić fstream na niebuforowane open / read / close POSIX. Dzieje się tak, ponieważ oba
basic_filebuf
iFILE
zawierają wewnętrzny bufor, który zostanie przydzielony przez standardowy alokator (a zatem nie zostanie usunięty z pamięci).Można to łatwo zrobić, zmieniając
sysrandom
na:Dzięki
Specjalne podziękowania dla Bena Voigta za wskazanie
FILE
zastosowań odczytów buforowanych i dlatego nie należy ich używać.Chciałbym również podziękować Peterowi Cordesowi za wspomnienie
getrandom
i brak OpenBSD/dev/urandom
.źródło
/dev/random
byłby lepszym wyborem do wysiewu RNG, ale najwyraźniej/dev/urandom
nadal jest uważany za bezpieczny obliczeniowo, nawet jeśli/dev/random
byłby blokowany z powodu niskiej dostępnej entropii, więcurandom
jest to zalecany wybór do wszystkiego, z wyjątkiem być może jednorazowych padów. Zobacz też unix.stackexchange.com/questions/324209/… . Uważaj jednak na przewidywalne nasiona odurandom
bardzo wczesnego czasu po uruchomieniu.getrandom(2)
Wywołanie systemowe Linuksa jest jak otwieranie i czytanie/dev/urandom
, z wyjątkiem tego, że będzie blokować, jeśli źródła losowości jądra nie zostały jeszcze zainicjowane. Myślę, że to oszczędza ci problemu z losowością niskiej jakości podczas wczesnego uruchamiania bez blokowania w innych przypadkach, takich jak/dev/random
robi./dev/urandom
zwykle działa. Python lista dyskusyjna dyskusja na ten temat jest coś, co zazwyczaj subskrybować: bugs.python.org/issue27266W pewnym sensie nie można tego zrobić przenośnie. Oznacza to, że można wyobrazić sobie w pełni deterministyczną platformę, na której działa C ++ (powiedzmy, symulator, który deterministycznie ustawia taktowanie zegara maszynowego i ze „zdeterminowanymi” I / O), w której nie ma źródła losowości do zapoczątkowania PRNG.
źródło
std::random_device
będzie w tej kategorii, ale najwyraźniej tak nie jest, jeśli niektóre rzeczywiste implementacje używają PRNG o stałym nasieniu! To wykracza daleko poza argument einpokluma.Możesz użyć a
std::seed_seq
i wypełnić go co najmniej do wymaganego rozmiaru stanu dla generatora, korzystając z metody uzyskania entropii Aleksandra Huszagha:Gdyby istniał właściwy sposób na wypełnienie lub utworzenie SeedSequence z UniformRandomBitGenerator w standardowej bibliotece, użycie
std::random_device
do prawidłowego wysiewu byłoby znacznie prostsze.źródło
Implementacja, nad którą pracuję, wykorzystuje
state_size
właściwośćmt19937
PRNG, aby zdecydować, ile nasion ma dostarczyć podczas inicjalizacji:Myślę, że jest miejsce na ulepszenia, ponieważ
std::random_device::result_type
mogą różnić sięstd::mt19937::result_type
rozmiarem i zakresem, więc należy to wziąć pod uwagę.Uwaga o std :: random_device .
Zgodnie z
C++11(/14/17)
normami:To oznacza, że realizacja może generować tylko deterministyczne wartości, jeżeli nie ma możliwości generowania niedeterministycznym te przez jakiś ograniczeń.
MinGW
Kompilator naWindows
pokazowo nie przewiduje niedeterministycznym wartości od jejstd::random_device
, mimo nich jest łatwo dostępne z poziomu systemu operacyjnego. Dlatego uważam to za błąd i prawdopodobnie nie jest to częste zjawisko w różnych implementacjach i platformach.źródło
std::random_device
, a zatem jest podatne na wynikające z niego problemy.std::random_device
? Wiem, że standard pozwala naPRNG
cofnięcie, ale czuję, że to tylko dla siebie, ponieważ trudno wymagać, aby każde urządzenie, które używa,C++
miało niedeterministyczne, losowe źródło. A jeśli nie, to co w ogóle możesz z tym zrobić?std::random_device
. Uważam, że to duch normy. Więc szukałem i mogę tylko znaleźć,MinGW
że jest zepsuty pod tym względem. Wygląda na to, że nikt nie zgłasza tego problemu z niczym innym, co znalazłem. Tak więc w mojej bibliotece po prostu oznaczyłemMinGW
jako nieobsługiwany. Gdyby istniał szerszy problem, przemyślę go ponownie. Po prostu nie widzę teraz na to dowodów.std::random_device
wszystkich, udostępniając go w formie, która nie zapewnia możliwości losowości platformy. Implementacje niskiej jakości są sprzeczne z celem istniejącego interfejsu API. Byłoby lepiej IMO, gdyby w ogóle go nie wdrożyli, dopóki nie zaczną działać. (Lub lepiej, jeśli API dostarcza sposób na niepowodzenie, jeśli żądanie wysokiej jakości losowość nie był dostępny, więc MinGW mógł uniknąć powodowania zagrożenia bezpieczeństwa, a jednocześnie dając różne nasiona do gier czy cokolwiek innego.)Nie ma nic złego w wysiewaniu przy użyciu czasu, zakładając, że nie potrzebujesz go do zabezpieczenia (i nie powiedziałeś, że jest to konieczne). Wgląd jest taki, że możesz użyć haszowania, aby naprawić nie-losowość. Zauważyłem, że działa to prawidłowo we wszystkich przypadkach, w tym iw szczególności w przypadku ciężkich symulacji Monte Carlo.
Jedną z fajnych cech tego podejścia jest to, że uogólnia ono inicjalizację z innych niezupełnie losowych zestawów nasion. Na przykład, jeśli chcesz, aby każdy wątek miał swój własny RNG (dla bezpieczeństwa wątków), możesz po prostu zainicjować na podstawie zahaszowanego identyfikatora wątku.
Poniżej znajduje się SSCCE , wydestylowane z mojego kodu (dla uproszczenia; niektóre struktury wspierające OO zostały usunięte):
źródło
1
a2
i obserwować, że sekwencja pływaków generowanych przez nich zajmuje trochę czasu, aby naprawdę rozchodzą).Oto moja własna odpowiedź na pytanie:
Pomysł polega na tym, aby użyć XOR do połączenia wielu potencjalnych źródeł entropii (szybki czas, powolny czas,
std::random-device
statyczne lokalizacje zmiennych, lokalizacje sterty, lokalizacje funkcji, lokalizacje bibliotek, wartości specyficzne dla programu), aby podjąć najlepszą próbę zainicjowania mt19937. Tak długo, jak przynajmniej raz źródło jest „dobre”, wynik będzie przynajmniej taki „dobry”.Ta odpowiedź nie jest tak krótka, jak byłoby to preferowane i może zawierać jeden lub więcej błędów logicznych. Więc uważam, że to praca w toku. Prosimy o komentarz, jeśli masz opinię.
źródło
&i ^ &myseed
powinien mieć znacznie mniejszą entropię niż jeden z nich samodzielnie, ponieważ oba są obiektami o statycznym czasie przechowywania w tej samej jednostce tłumaczeniowej, a zatem prawdopodobnie są raczej blisko siebie. I wydaje się, że w rzeczywistości nie używasz specjalnej wartości z inicjalizacjimyseed
?^
jest okropnym łącznikiem mieszania; jeśli dwie wartości mają dużo entropii, ale niewiele w porównaniu ze sobą, usuwa ją.+
jest zwykle lepszy (ponieważ x + x spala tylko 1 bit entropii w x, podczas gdy x ^ x spala je wszystkie). Podejrzewam, że funkcja nie jest tak bezpieczna (rd()
)+
mam na myśli na niepodpisanym (+
na podpisanym jest przynęta UB). Chociaż są to dość śmieszne przypadki UB, powiedziałeś, że są przenośne. Rozważ również uzyskanie adresu funkcji jako wartości całkowitej, jeśli to możliwe (nie/dev/urandom
lub/dev/random
).Są one dostępne w nowoczesnych systemach typu UNIX, takich jak Linux, Solaris i OpenBSD.
źródło
Dana platforma może mieć źródło entropii, takie jak
/dev/random
. Nanosekundy od Epoki zstd::chrono::high_resolution_clock::now()
to prawdopodobnie najlepsze ziarno w Bibliotece Standardowej.Wcześniej używałem czegoś w rodzaju,
(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )
aby uzyskać więcej bitów entropii dla aplikacji, które nie są krytyczne dla bezpieczeństwa.źródło
/dev/urandom
, szczególnie w takim przypadku./dev/random
bloki, i często bez dobrego powodu ([wstaw długie wyjaśnienie, ile różnych systemów operacyjnych szacuje losowość bajtów wytwarzanych przez / dev / random])./dev/urandom
nie było, a alternatywą dla blokowania był determinizm. Pudełko może mieć/dev/hwrng
lub/dev/hw_random
też, co powinno być jeszcze lepsze./dev/random
” i wydaje się, że wywołało to świętą wojnę w/dev/random
porównaniu/dev/urandom
z Linuksem, której nie zamierzałem,