Ich habe eine Antwort geschrieben, die ausführlich beschreibt, wie getrandom()
Blöcke warten auf anfängliche Entropie.
Ich denke jedoch, dass er urandom leicht übertreibt, indem er sagt, dass "der einzige Moment, in dem /dev/urandom aufgrund niedriger Entropie auf ein Sicherheitsproblem hindeuten könnte, in den ersten Momenten einer neuen, automatisierten Betriebssysteminstallation ist".
Ihre Sorgen sind begründet. Ich habe eine offene Frage zu genau dieser Sache und ihren Auswirkungen. Das Problem ist, dass der Persistent Random Seed ziemlich lange braucht, um vom Input-Pool zum Output-Pool (dem Blocking-Pool und dem CRNG) zu wechseln. Dieses Problem bedeutet, dass /dev/urandom
gibt einige Minuten nach dem Booten potenziell vorhersagbare Werte aus. Die Lösung besteht, wie Sie sagen, darin, entweder den blockierenden /dev/random
zu verwenden , oder verwenden Sie getrandom()
auf Sperren setzen.
Tatsächlich ist es nicht ungewöhnlich, Zeilen wie diese im Log des Kernels beim frühen Booten zu sehen:
random: sn: uninitialized urandom read (4 bytes read, 7 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 15 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 16 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 16 bits of entropy available)
random: sn: uninitialized urandom read (4 bytes read, 20 bits of entropy available)
All dies sind Fälle, in denen auf den nicht blockierenden Pool zugegriffen wurde, noch bevor genügend Entropie gesammelt wurde. Das Problem ist, dass die Menge an Entropie einfach zu gering ist, um an dieser Stelle kryptografisch ausreichend sicher zu sein. Es sollte 2 mögliche 4-Byte-Werte geben, jedoch mit nur 7 verfügbaren Entropiebits, dh es gibt nur 2 oder 128 verschiedene Möglichkeiten.
Halderman scheint auch zu sagen, dass sich der Entropiepool bei jedem Start füllt und nicht, wie Pornin in seiner Antwort sagt, bei der allerersten Betriebssysteminstallation. Obwohl es für meine Anwendung nicht besonders wichtig ist, frage ich mich:Was ist das?
Es ist eigentlich eine Frage der Semantik. Der eigentliche Entropie Pool (die im Kernel gespeicherte Speicherseite, die zufällige Werte enthält) wird bei jedem Start durch den persistenten Entropie-Seed und durch Umgebungsrauschen gefüllt. Allerdings ist die Entropie Seed selbst ist eine Datei, die während der Installation erstellt und bei jedem Herunterfahren des Systems mit neuen zufälligen Werten aktualisiert wird. Ich stelle mir vor, dass Pornin den zufälligen Seed als Teil des Entropiepools betrachtet (wie in einem Teil des allgemeinen Entropieverteilungs- und -sammelsystems), während Halderman ihn als separat betrachtet (weil der Entropiepool technisch gesehen eine Seite von ist). Erinnerung, mehr nicht). Die Wahrheit ist, dass der Entropiesamen bei jedem Start in den Entropiepool eingespeist wird, aber es kann einige Minuten dauern, bis der Pool tatsächlich beeinflusst wird.
Eine Zusammenfassung der drei Quellen der Zufälligkeit:
-
/dev/random
- Das Blockierungszeichengerät verringert jedes Mal, wenn es gelesen wird, einen "Entropiezählwert" (obwohl die Entropie nicht wirklich erschöpft ist). Es blockiert jedoch auch, bis beim Booten genügend Entropie gesammelt wurde, was eine frühzeitige Verwendung sicher macht. -
/dev/urandom
- Das nicht blockierende Zeichengerät gibt zufällige Daten aus, wenn jemand davon liest. Sobald genügend Entropie gesammelt wurde, wird ein praktisch unbegrenzter Strom ausgegeben, der nicht von zufälligen Daten zu unterscheiden ist. Leider ist es aus Kompatibilitätsgründen sogar früh beim Booten lesbar, bevor genügend einmalige Entropie gesammelt wurde. -
getrandom()
- Ein Systemaufruf, der Zufallsdaten ausgibt, solange der Entropiepool ordnungsgemäß mit der erforderlichen Mindestmenge an Entropie initialisiert wurde. Es liest standardmäßig aus dem nicht blockierenden Pool. Bei Angabe desGRND_NONBLOCK
Flag, wird ein Fehler zurückgegeben, wenn nicht genügend Entropie vorhanden ist. Bei Angabe desGRND_RANDOM
Flag, verhält es sich identisch zu/dev/random
, einfach blockieren, bis Entropie verfügbar ist.
Ich schlage vor, Sie verwenden die dritte Option, den getrandom()
Systemaufruf. Dies ermöglicht es einem Prozess, kryptografisch sichere Zufallsdaten mit hoher Geschwindigkeit zu lesen, und blockiert nur früh beim Booten, wenn nicht genug Entropie gesammelt wurde. Wenn Pythons os.urandom()
Funktion fungiert als Wrapper für diesen Systemaufruf, wie Sie sagen, dann sollte es in Ordnung sein, es zu verwenden. Es sieht so aus, als ob tatsächlich viel darüber diskutiert wurde, ob dies der Fall sein sollte oder nicht, was dazu führte, dass es blockiert, bis genügend Entropie verfügbar ist.
Etwas weiter gedacht:Was sind die besten Praktiken für Umgebungen, die so frisch und naiv sind, wie ich oben beschrieben habe, die aber auf Geräten mit ziemlich miserablen Aussichten für die anfängliche Entropieerzeugung laufen?
Dies ist eine häufige Situation, und es gibt einige Möglichkeiten, damit umzugehen:
-
Stellen Sie sicher, dass Sie beim frühen Booten blockieren, indem Sie beispielsweise
/dev/random
verwenden odergetrandom()
. -
Behalten Sie, wenn möglich, einen dauerhaften zufälligen Seed bei (d. h. wenn Sie bei jedem Start in den Speicher schreiben können).
-
Am wichtigsten ist, einen Hardware-RNG zu verwenden . Dies ist die effektivste Maßnahme Nr. 1.
Die Verwendung eines Hardware-Zufallszahlengenerators ist sehr wichtig. Der Linux-Kernel initialisiert seinen Entropie-Pool mit jeder unterstützten HWRNG-Schnittstelle, falls vorhanden, wodurch das Boot-Entropie-Loch vollständig beseitigt wird. Viele eingebettete Geräte haben ihre eigenen Zufallsgeneratoren.
Dies ist besonders wichtig für viele eingebettete Geräte, da sie möglicherweise keinen hochauflösenden Timer haben, der für den Kernel erforderlich ist, um sicher Entropie aus Umgebungsgeräuschen zu erzeugen. Einige Versionen von MIPS-Prozessoren haben beispielsweise keinen Zykluszähler.
Wie und warum schlagen Sie vor, Urandom zu verwenden, um ein (ich schätze Userland?) CSPRNG zu gründen? Wie wird dieser Beat zufällig?
Das nicht blockierende Zufallsgerät ist nicht für hohe Leistung ausgelegt. Bis vor kurzem war das Gerät obszön langsam, da es SHA-1 für Zufälligkeiten anstelle einer Stream-Chiffre verwendete, wie es jetzt der Fall ist. Die Verwendung einer Kernel-Schnittstelle für Zufälligkeit kann weniger effizient sein als ein lokales CSPRNG im Benutzerbereich, da jeder Aufruf an den Kernel einen teuren Kontextwechsel erfordert. Der Kernel wurde entwickelt, um Anwendungen zu berücksichtigen, die stark daraus schöpfen wollen, aber die Kommentare im Quellcode machen deutlich, dass sie dies nicht als das Richtige ansehen:
/*
* Hack to deal with crazy userspace progams when they are all trying
* to access /dev/urandom in parallel. The programs are almost
* certainly doing something terribly wrong, but we'll work around
* their brain damage.
*/
Beliebte Kryptobibliotheken wie OpenSSL unterstützen das Generieren von Zufallsdaten. Sie können einmal geseedet oder gelegentlich neu geseedet werden und können stärker von der Parallelisierung profitieren. Es ermöglicht außerdem, portablen Code zu schreiben, der nicht auf das Verhalten eines bestimmten Betriebssystems oder einer bestimmten Betriebssystemversion angewiesen ist.
Wenn Sie keine großen Mengen an Zufälligkeit benötigen, ist es völlig in Ordnung, die Schnittstelle des Kernels zu verwenden. Wenn Sie eine Kryptoanwendung entwickeln, die während ihrer gesamten Lebensdauer viel Zufälligkeit benötigt, möchten Sie vielleicht eine Bibliothek wie OpenSSL verwenden, um dies für Sie zu erledigen.
Es gibt drei Zustände, in denen sich das System befinden kann:
- Hat nicht genug Entropie gesammelt, um ein CPRNG sicher zu initialisieren.
-
Genügend Entropie gesammelt hat, um ein CPRNG sicher zu initialisieren, und:
2a. Hat mehr Entropie abgegeben als gesammelt.
2b. Hat weniger Entropie abgegeben als gesammelt.
Historisch hielt man die Unterscheidung zwischen (2a) und (2b) für wichtig. Dies verursachte zwei Probleme. Erstens ist es falsch – die Unterscheidung ist für ein richtig konzipiertes CPRNG bedeutungslos. Und zweitens hat die Betonung der Unterscheidung (2a)-vs-(2b) dazu geführt, dass die Leute die Unterscheidung zwischen (1) und (2) übersehen haben, was eigentlich sehr wichtig ist. Die Leute kollabierten einfach (1) zu einem Sonderfall von (2a).
Was Sie wirklich wollen, ist etwas, das im Zustand (1) blockiert und in den Zuständen (2a) oder (2b) nicht blockiert.
Leider bedeutete die Verwechslung zwischen (1) und (2a) früher, dass dies keine Option war. Ihre einzigen beiden Optionen waren /dev/random
, die in den Fällen (1) und (2a) blockierte, und /dev/urandom
, die nie blockiert hat. Aber Zustand (1) passiert fast nie – und kommt in gut konfigurierten Systemen überhaupt nicht vor, siehe unten – dann /dev/urandom
ist für fast alle Systeme fast immer besser. Daher kamen all diese Blogposts über „Urandom immer verwenden“ – sie versuchten, die Leute davon zu überzeugen, keine bedeutungslose und schädliche Unterscheidung zwischen den Zuständen (2a) und (2b) zu machen.
Aber ja, beides ist nicht das, was Sie wirklich wollen. Also das neuere getrandom
syscall, der standardmäßig im Zustand (1) blockiert und in den Zuständen (2a) oder (2b) nicht blockiert. Unter modernem Linux sollte die Orthodoxie also aktualisiert werden auf:immer getrandom
verwenden mit Standardeinstellungen .
Zusätzliche Falten:
-
getrandom
unterstützt auch einen Nicht-Standardmodus, in dem es sich wie/dev/random
verhält , die über denGRND_RANDOM
angefordert werden kann Flagge. AFAIK dieses Flag ist nie wirklich nützlich, aus den gleichen Gründen, die in den alten Blog-Posts beschrieben wurden. Nicht verwenden. -
getrandom
hat auch einige zusätzliche Bonusvorteile gegenüber/dev/urandom
:Es funktioniert unabhängig von Ihrem Dateisystem-Layout und erfordert kein Öffnen eines Dateideskriptors. Beides ist problematisch für generische Bibliotheken, die nur minimale Annahmen über die Umgebung treffen möchten, in der sie verwendet werden. Dies wirkt sich nicht auf die kryptografische Sicherheit aus , aber betrieblich ist es nett. -
Ein gut konfiguriertes System wird immer Entropie zur Verfügung haben, selbst beim frühen Booten (d.h. Sie sollten niemals in den Zustand (1) gelangen). Es gibt viele Möglichkeiten, dies zu verwalten:Speichern Sie etwas Entropie vom vorherigen Start, um sie beim nächsten zu verwenden. Installieren Sie einen Hardware-RNG. Docker-Container verwenden den Kernel des Hosts und erhalten so Zugriff auf seinen Entropiepool. Hochwertige Virtualisierungs-Setups haben Möglichkeiten, das Gastsystem über Hypervisor-Schnittstellen Entropie vom Host-System holen zu lassen (z. B. Suche nach "virtio rng"). Aber natürlich sind nicht alle Systeme gut konfiguriert. Wenn Sie ein schlecht konfiguriertes System haben, sollten Sie versuchen, es stattdessen gut zu konfigurieren. Im Prinzip sollte dies einfach billig sein, aber in Wirklichkeit räumen die Leute der Sicherheit keine Priorität ein, so dass es erforderlich sein kann, Dinge wie den Wechsel des Cloud-Anbieters oder den Wechsel zu einer anderen eingebetteten Plattform zu tun. Und leider stellen Sie möglicherweise fest, dass dies teurer ist, als Sie (oder Ihr Chef) zu zahlen bereit sind, sodass Sie sich mit einem schlecht konfigurierten System herumschlagen müssen. Mein Mitgefühl, wenn ja.
-
Wie @forest anmerkt, wenn Sie viele CPRNG-Werte benötigen, können Sie dies beschleunigen, wenn Sie sehr vorsichtig sind, indem Sie Ihr eigenes CPRNG im Benutzerbereich ausführen, während Sie
getrandom
verwenden zum (Nach-)Säen. Dies ist jedoch eher eine Sache „nur für Experten“, genau wie jede Situation, in der Sie feststellen, dass Sie Ihre eigenen Krypto-Primitive implementieren. Sie sollten dies nur tun, wenn Sie dies mitgetrandom
gemessen und festgestellt haben direkt ist zu langsam für Ihre Anforderungen und Sie verfügen über erhebliches kryptografisches Fachwissen. Es ist sehr einfach, eine CPRNG-Implementierung so zu vermasseln, dass Ihre Sicherheit vollständig gebrochen ist, aber die Ausgabe immer noch zufällig "aussieht", sodass Sie es nicht bemerken.