Ich sehe viele Antworten, in denen jemand vorschlägt <random>
, Zufallszahlen zu generieren, normalerweise zusammen mit Code wie diesem:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
Normalerweise ersetzt dies eine Art "unheiligen Greuel" wie:
srand(time(NULL));
rand()%6;
Wir könnten den alten Weg kritisieren , indem wir argumentieren, dass er time(NULL)
eine niedrige Entropie liefert, time(NULL)
vorhersehbar ist und das Endergebnis ungleichmäßig ist.
Aber all das gilt für den neuen Weg: Er hat nur ein glänzenderes Furnier.
rd()
gibt eine einzelne zurückunsigned int
. Dies hat mindestens 16 Bit und wahrscheinlich 32. Das reicht nicht aus, um die 19937-Bit-Zustände von MT zu setzen.Die Verwendung
std::mt19937 gen(rd());gen()
(Seeding mit 32 Bit und Betrachten der ersten Ausgabe) ergibt keine gute Ausgabeverteilung. 7 und 13 können niemals die erste Ausgabe sein. Zwei Samen produzieren 0. Zwölf Samen produzieren 1226181350. ( Link )std::random_device
kann und wird manchmal als einfaches PRNG mit einem festen Startwert implementiert. Es kann daher sein, dass bei jedem Lauf dieselbe Sequenz erzeugt wird. ( Link ) Das ist noch schlimmer alstime(NULL)
.
Schlimmer noch, es ist sehr einfach, die oben genannten Codefragmente zu kopieren und einzufügen, trotz der darin enthaltenen Probleme. Einige Lösungen hierfür erfordern den Erwerb größerer Bibliotheken, die möglicherweise nicht für jeden geeignet sind.
Vor diesem Hintergrund lautet meine Frage: Wie kann man das mt19937 PRNG in C ++ kurz, portabel und gründlich aussäen?
Angesichts der oben genannten Probleme eine gute Antwort:
- Muss den mt19937 / mt19937_64 vollständig aussäen.
- Kann sich nicht nur auf
std::random_device
odertime(NULL)
als Entropiequelle verlassen. - Sollte sich nicht auf Boost oder andere Bibliotheken verlassen.
- Sollte in eine kleine Anzahl von Zeilen passen, so dass es gut aussehen würde, wenn es in eine Antwort eingefügt wird.
Gedanken
Mein aktueller Gedanke ist, dass Ausgaben von
std::random_device
(möglicherweise über XOR) mittime(NULL)
Werten, die aus der Adressraum-Randomisierung abgeleitet wurden , und einer fest codierten Konstante (die während der Verteilung festgelegt werden kann) kombiniert werden können , um eine bestmögliche Entropie zu erzielen.std::random_device::entropy()
gibt keinen guten Hinweis darauf, wasstd::random_device
tun könnte oder nicht.
std::random_device
,time(NULL)
und Funktionsadressen, dann XORed zusammen eine Art Best-Effort Entropiequelle zu erzeugen.std::random_device
die Plattformen, auf denen Sie Ihr Programm ausführen möchten , ordnungsgemäß zu implementieren und eine Hilfsfunktion bereitzustellen, die einen Seed-Generator (seed11::make_seeded<std::mt19937>()
) erstelltAntworten:
Ich würde argumentieren, dass der größte Fehler
std::random_device
darin besteht, dass ein deterministischer Fallback zulässig ist, wenn kein CSPRNG verfügbar ist. Dies allein ist ein guter Grund, ein PRNG nicht mit zu setzenstd::random_device
, da die erzeugten Bytes deterministisch sein können. Leider bietet es keine API, um herauszufinden, wann dies geschieht, oder um Fehler anstelle von Zufallszahlen geringer Qualität anzufordern.Das heißt, es gibt keine vollständig tragbare Lösung: Es gibt jedoch einen anständigen, minimalen Ansatz. Sie können einen minimalen Wrapper um ein CSPRNG (wie
sysrandom
unten definiert ) verwenden, um das PRNG zu setzen.Windows
Sie können sich auf
CryptGenRandom
ein CSPRNG verlassen. Beispielsweise können Sie den folgenden Code verwenden:Unix-ähnlich
Auf vielen Unix-ähnlichen Systemen sollten Sie nach Möglichkeit / dev / urandom verwenden (obwohl dies auf POSIX-kompatiblen Systemen nicht garantiert ist).
Andere
Wenn kein CSPRNG verfügbar ist, können Sie sich darauf verlassen
std::random_device
. Ich würde dies jedoch nach Möglichkeit vermeiden, da verschiedene Compiler (insbesondere MinGW) es als PRNG implementieren (tatsächlich wird jedes Mal dieselbe Sequenz erstellt, um die Menschen darauf aufmerksam zu machen, dass es nicht richtig zufällig ist).Aussaat
Jetzt, da wir unsere Stücke mit minimalem Overhead haben, können wir die gewünschten Bits zufälliger Entropie erzeugen, um unser PRNG zu säen. In diesem Beispiel werden (offensichtlich unzureichende) 32-Bit-Werte zum Setzen des PRNG verwendet, und Sie sollten diesen Wert erhöhen (abhängig von Ihrem CSPRNG).
Vergleich zu Boost
Nach einem kurzen Blick auf den Quellcode können wir Parallelen zu boost :: random_device (ein echtes CSPRNG) erkennen . Boost verwendet
MS_DEF_PROV
unter Windows, dem Anbietertyp fürPROV_RSA_FULL
. Das einzige, was fehlt, wäre die Überprüfung des kryptografischen Kontexts, mit dem gearbeitet werden kannCRYPT_VERIFYCONTEXT
. Unter * Nix verwendet Boost/dev/urandom
. IE, diese Lösung ist portabel, gut getestet und einfach zu bedienen.Linux-Spezialisierung
Wenn Sie bereit sind, Prägnanz für Sicherheit zu opfern,
getrandom
ist dies eine ausgezeichnete Wahl unter Linux 3.17 und höher sowie unter Solaris.getrandom
verhält sich identisch mit/dev/urandom
, außer dass es blockiert, wenn der Kernel sein CSPRNG nach dem Booten noch nicht initialisiert hat. Das folgende Snippet erkennt, ob Linuxgetrandom
verfügbar ist, und greift nicht darauf zurück/dev/urandom
.OpenBSD
Es gibt eine letzte Einschränkung: moderne OpenBSD hat keine
/dev/urandom
. Sie sollten stattdessen getentropy verwenden.andere Gedanken
Wenn Sie kryptografisch sichere Zufallsbytes benötigen, sollten Sie den fstream wahrscheinlich durch das ungepufferte Öffnen / Lesen / Schließen von POSIX ersetzen. Dies liegt daran, dass beide
basic_filebuf
undFILE
einen internen Puffer enthalten, der über einen Standard-Allokator zugewiesen wird (und daher nicht aus dem Speicher gelöscht wird).Dies kann leicht durch Ändern
sysrandom
von:Vielen Dank
Besonderer Dank geht an Ben Voigt für den Hinweis, dass
FILE
gepufferte Lesevorgänge verwendet werden und daher nicht verwendet werden sollten.Ich möchte auch Peter Cordes für die Erwähnung
getrandom
und das Fehlen von OpenBSD danken/dev/urandom
.quelle
/dev/random
wäre die bessere Wahl für das Seeding eines RNG, wird aber anscheinend/dev/urandom
immer noch als rechnersicher angesehen, selbst wenn/dev/random
es aufgrund der geringen verfügbaren Entropie blockieren würde. Daherurandom
ist dies die empfohlene Wahl für alles außer vielleicht einmaligen Pads. Siehe auch unix.stackexchange.com/questions/324209/… .urandom
Achten Sie jedoch schon sehr früh nach dem Start auf vorhersehbare Samen .getrandom(2)
Systemaufruf von Linux ist wie das Öffnen und Lesen/dev/urandom
, außer dass er blockiert wird, wenn die Zufallsquellen des Kernels noch nicht initialisiert wurden. Ich denke, dies erspart Ihnen das Problem der Zufälligkeit bei geringer Qualität beim frühen Booten, ohne in anderen Fällen zu blockieren/dev/random
./dev/urandom
Allgemeinen funktioniert. Die Python-Mailinglistendiskussion darüber ist etwas, das ich im Allgemeinen abonniere: bugs.python.org/issue27266In gewissem Sinne kann dies nicht portabel gemacht werden. Das heißt, man kann sich eine gültige vollständig deterministische Plattform vorstellen, auf der C ++ ausgeführt wird (z. B. ein Simulator, der den Maschinentakt deterministisch und mit "determinierter" E / A steuert), bei der es keine Zufallsquelle gibt, um ein PRNG zu setzen.
quelle
std::random_device
dies in dieser Kategorie liegt, aber anscheinend ist es nicht so, dass einige echte Implementierungen ein PRNG mit festem Startwert verwenden! Das geht weit über Einpoklums Argumentation hinaus.Sie können a verwenden
std::seed_seq
und es mindestens auf die erforderliche Zustandsgröße für den Generator füllen, indem Sie die Methode von Alexander Huszagh verwenden, um die Entropie zu erhalten:Wenn es einen geeigneten Weg gäbe , eine SeedSequence aus einem UniformRandomBitGenerator in der Standardbibliothek zu füllen oder zu erstellen, wäre die Verwendung
std::random_device
für das ordnungsgemäße Seeding viel einfacher.quelle
Die Implementierung, an der ich arbeite, nutzt die
state_size
Eigenschaft desmt19937
PRNG, um zu entscheiden, wie viele Seeds bei der Initialisierung bereitgestellt werden sollen:Ich denke, es gibt Raum für Verbesserungen, da diese
std::random_device::result_type
sichstd::mt19937::result_type
in Größe und Reichweite unterscheiden können, sodass dies wirklich berücksichtigt werden sollte.Ein Hinweis zu std :: random_device .
Gemäß den
C++11(/14/17)
Standards:Dies bedeutet, dass die Implementierung möglicherweise nur deterministische Werte generiert , wenn durch eine Einschränkung verhindert wird, dass nicht deterministische Werte generiert werden .
Der
MinGW
Compiler onWindows
liefert bekanntlich keine nicht deterministischen Werte von ihmstd::random_device
, obwohl sie vom Betriebssystem leicht verfügbar sind. Daher halte ich dies für einen Fehler und wahrscheinlich nicht für ein häufiges Auftreten zwischen Implementierungen und Plattformen.quelle
std::random_device
und ist daher anfällig für daraus resultierende Probleme.std::random_device
? Ich weiß, dass der Standard einenPRNG
Rückfall zulässt, aber ich bin der Meinung, dass dies nur dazu dient, sich selbst zu schützen, da es schwierig ist zu verlangen, dass jedes Gerät, das verwendet,C++
eine nicht deterministische Zufallsquelle hat. Und wenn nicht, was könnten Sie dann überhaupt dagegen tun?std::random_device
. Ich glaube, das ist der Geist des Standards. Also habe ich gesucht und kann nur feststellen,MinGW
dass diesbezüglich kaputt ist. Niemand scheint dieses Problem mit irgendetwas anderem zu melden, das ich gefunden habe. Daher habe ich in meiner Bibliothek einfachMinGW
als nicht unterstützt markiert . Wenn es ein größeres Problem gäbe, würde ich es überdenken. Ich sehe gerade keine Beweise dafür.std::random_device
für alle ruiniert , indem es in einer Form verfügbar gemacht wird, die nicht die Zufälligkeitsfunktionen der Plattform bietet. Implementierungen mit geringer Qualität beeinträchtigen den Zweck der vorhandenen API. Es wäre besser, IMO, wenn sie es erst dann implementieren würden, wenn es funktioniert. (Oder besser, wenn die API eine Möglichkeit bietet, einen Fehler anzufordern, wenn keine qualitativ hochwertige Zufälligkeit verfügbar ist, sodass MinGW Sicherheitsrisiken vermeiden kann, während immer noch andere Startwerte für Spiele oder was auch immer angegeben werden.)Es ist nichts Falsches daran, Zeit zu verwenden, vorausgesetzt, Sie brauchen sie nicht, um sicher zu sein (und Sie haben nicht gesagt, dass dies notwendig ist). Die Erkenntnis ist, dass Sie Hashing verwenden können, um die Nicht-Zufälligkeit zu beheben. Ich habe festgestellt, dass dies in allen Fällen angemessen funktioniert, auch und insbesondere für schwere Monte-Carlo-Simulationen.
Ein nettes Merkmal dieses Ansatzes ist, dass er auf die Initialisierung von anderen nicht wirklich zufälligen Sätzen von Samen verallgemeinert wird. Wenn Sie beispielsweise möchten, dass jeder Thread über ein eigenes RNG verfügt (aus Gründen der Thread-Sicherheit), können Sie die Initialisierung nur anhand der Hash-Thread-ID durchführen.
Das Folgende ist eine SSCCE , die aus meiner Codebasis destilliert wurde (der Einfachheit halber wurden einige OO-Unterstützungsstrukturen entfernt):
quelle
1
und2
und beobachten Sie, dass es eine Weile dauert, bis die von ihnen erzeugte Reihenfolge der Floats wirklich divergiert).Hier ist mein eigener Stich bei der Frage:
Die Idee hier ist, XOR zu verwenden, um viele potenzielle Entropiequellen (schnelle Zeit, langsame Zeit,
std::random-device
statische variable Positionen, Heap-Positionen, Funktionspositionen, Bibliothekspositionen, programmspezifische Werte) zu kombinieren , um einen bestmöglichen Versuch zur Initialisierung der zu unternehmen mt19937. Solange mindestens eine Quelle "gut" ist, ist das Ergebnis mindestens "gut".Diese Antwort ist nicht so kurz wie vorzuziehen und kann einen oder mehrere Logikfehler enthalten. Ich halte es also für eine laufende Arbeit. Bitte kommentieren Sie, wenn Sie Feedback haben.
quelle
&i ^ &myseed
dass die Entropie erheblich geringer sein sollte als bei beiden allein, da beide Objekte statische Speicherdauer in derselben Übersetzungseinheit haben und daher wahrscheinlich ziemlich nahe beieinander liegen. Und Sie scheinen den speziellen Wert aus der Initialisierung von nicht wirklich zu verwendenmyseed
?^
ist ein schrecklicher Hash-Kombinierer; Wenn zwei Werte viel Entropie haben, aber im Vergleich wenig, werden sie entfernt.+
ist normalerweise besser (da x + x nur 1 Bit Entropie in x verbrennt, während x ^ x sie alle verbrennt). Ich vermute, dass die Funktion nicht sicher ist (rd()
)+
meine ich auf unsigniert (+
auf signiert ist UB-Köder). Während dies etwas lächerliche UB-Fälle sind, haben Sie gesagt, tragbar./dev/urandom
oder/dev/random
).Diese sind auf modernen UNIX-ähnlichen Systemen wie Linux, Solaris und OpenBSD verfügbar.
quelle
Eine bestimmte Plattform kann eine Entropiequelle haben, wie z
/dev/random
. Nanosekunden seit der Epoche mitstd::chrono::high_resolution_clock::now()
ist wahrscheinlich der beste Samen in der Standardbibliothek.Ich habe zuvor so etwas verwendet
(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )
, um mehr Entropie für Anwendungen zu erhalten, die nicht sicherheitskritisch sind.quelle
/dev/urandom
, besonders in einem Fall wie diesem./dev/random
Blöcke, und oft ohne gute Gründe dafür ([fügen Sie eine lange Erklärung darüber ein, wie viele verschiedene Betriebssysteme die Zufälligkeit der von / dev / random erzeugten Bytes schätzen])./dev/urandom
es keine gab, und die Alternative zum Blockieren war Determinismus. Eine Box könnte/dev/hwrng
oder/dev/hw_random
auch haben, was noch besser sein sollte./dev/random
", und das scheint einen heiligen Krieg/dev/random
gegen/dev/urandom
Linux ausgelöst zu haben , den ich nicht beabsichtigt hatte, als ich dieses Beispiel gab.