- Ein Experiment, bei dem die zwei für einen 2048-Bit-RSA-Schlüssel benötigten Primzahlen von jeweils etwa 1024 Bit direkt in Rust erzeugt wurden und dafür alles von der Zufallszahlenerzeugung bis zur Arithmetik großer Ganzzahlen ohne externe Abhängigkeiten implementiert wurde
- Eine einfache trial division reichte bei 16 Bit mit rund 40 ms aus, brauchte aber selbst bei 64 Bit nach Optimierungen noch 6,4 Sekunden und ließ sich daher kaum auf 1024 Bit skalieren
- Der Fermat-Test ist schnell, kann aber Pseudoprimzahlen nicht zuverlässig aussortieren, daher wurde für die endgültige Entscheidung ein Miller-Rabin-Test mit k=10 verwendet
- Um die Grenzen der eingebauten Ganzzahltypen zu überwinden, wurde ein eigenes BigInt gebaut; durch den Wechsel von bool-Arrays zu Byte-Arrays und schließlich zu
u64-Chunk-Strukturen sank die Zeit zur Erzeugung einer 1024-Bit-Primzahl von 32 Minuten auf 60–90 Sekunden - Die finale Implementierung kombiniert ein BigInt mit
u64-Chunks, schnelle Division, trial division mit kleinen Primzahlen, das Erhöhen des Kandidatenwerts um+2und parallele Ausführung mit 16 Threads und fand so im Mittel in etwa 40 ms eine 1024-Bit-Primzahl, ist aber keine validierte Kryptobibliothek
1024-Bit-Primzahlen für RSA selbst erzeugen
- Ziel war es, Primzahlen zu erzeugen, die sich für die RSA-Schlüsselerzeugung verwenden lassen
- Ein 2048-Bit-RSA-Schlüssel entsteht als Produkt zweier Primzahlen, daher werden zwei Primzahlen mit jeweils ungefähr 1024 Bit benötigt
- Damit verengte sich die Herausforderung ganz natürlich auf die Erzeugung von 1024-Bit-Primzahlen
- Für das Experiment wurden drei Einschränkungen gesetzt
- Der Code wird von Grund auf selbst geschrieben und verwendet keine externen Abhängigkeiten
- Es wird ein Laptop mit AMD-Ryzen-7-CPU und 16 GB RAM verwendet, ohne externe Hardware oder Cloud
- Die Primzahl muss in „vernünftiger Zeit“ erzeugt werden
- Als Sprache wurde Rust gewählt, das der Autor gerade lernte
- Es erschien nah genug an Low-Level-Konzepten und zugleich hoch genug, um Codefragmente gut verstehen zu können
Die Grenzen von trial division bei 16 und 64 Bit
- Der grundlegende Ablauf bestand darin, wiederholt eine N-Bit-Zufallszahl zu erzeugen und zu stoppen, sobald sie einen Primzahltest besteht
- Die Zufallszahlen wurden nicht mit Rusts
rand-Crate erzeugt, sondern durch direktes Lesen aus Linux-/dev/urandom/dev/urandomist eine Pseudogerätedatei für den Zugriff auf den CSPRNG des Linux-Kernels- Der Kernel sammelt Entropie aus der Benutzerumgebung und setzt einen auf ChaCha20 basierenden deterministischen Stromchiffre-Generator periodisch neu
- Bei 16-Bit-Zufallszahlen wurden das erste und das letzte Bit auf
1gesetzt- Das letzte Bit
1garantiert eine ungerade Zahl - Das erste Bit
1stellt sicher, dass der gesamte benötigte Bitbereich genutzt wird
- Das letzte Bit
- Bei 16 Bit wurde schon mit bloßer trial division durch alle Zahlen von
3bissqrt(num)in etwa 40 ms eine Primzahl gefunden- Ein Beispiel ergab
Prime found: 44809, die Gesamtzeit lag bei etwa 0,038 Sekunden
- Ein Beispiel ergab
- Bei der Erweiterung auf 64 Bit dauerte einfache trial division rund 30 Sekunden
- Danach wurde verbessert, indem nur noch Kandidaten der Form
6k±1geprüft und zunächst durch eine Liste kleiner Primzahlen geteilt wurden - Nach diesen Verbesserungen dauerte die Erzeugung einer 64-Bit-Primzahl 6,414 Sekunden
- Danach wurde verbessert, indem nur noch Kandidaten der Form
- Schon bei 64 Bit mit 6 Sekunden wurde klar, dass sich dieser Ansatz nicht sinnvoll bis zur Erzeugung von 1024-Bit-Primzahlen skalieren lässt
Wechsel zu probabilistischen Primzahltests
- Es wurden zwar deterministische Algorithmen wie APR-CL und ECPP betrachtet, sie erwiesen sich aber als mathematisch komplex und schwer zugänglich für eine eigene Implementierung
- Nach dem Blick in den OpenSSL-Quellcode und die NIST-Empfehlungen zeigte sich, dass in realen Anwendungen einschließlich RSA probabilistische Primzahltests weit verbreitet sind
- Der Algorithmus wechselte damit von „beweist, dass die Zahl prim ist“ zu „stuft sie mit gegebener Genauigkeit als probable prime ein“
-
Fermat-Test
- Das kleine Theorem von Fermat nutzt die Beziehung, dass für eine Primzahl
pund eina, das nicht durchpteilbar ist,a^(p-1) = 1 mod pgilt - Da einfache Potenzierung bei
u128überläuft, wurde modulare Exponentiation implementiert pow()erwartet einen Exponenten vom Typu32, und das Potenzieren vonu128mit größeren Exponenten kann zu Überläufen führen- Auch die Multiplikation selbst kann den
u128-Bereich überschreiten, weshalb vorübergehend nur 64-Bit-Zahlen innerhalb vonu128gespeichert wurden - Der Fermat-Test ist schnell, kann aber wegen Fermat-Pseudoprimzahlen zusammengesetzte Zahlen fälschlich als prim einstufen
- Solche zusammengesetzten Zahlen sind selten, aber häufig genug, dass der Fermat-Test allein als nicht zuverlässig genug angesehen wurde
- Das kleine Theorem von Fermat nutzt die Beziehung, dass für eine Primzahl
-
Miller-Rabin-Test
- Miller-Rabin basiert auf derselben Grundidee wie der Fermat-Test, ist aber ein stärkerer probabilistischer Primzahltest
- Die Implementierung zerlegt
n-1in die Form2^s × dund prüft dann mehrere Bedingungen a^d = 1 mod n- oder für ein
0 <= r < sgilta^(2^r × d) = n - 1 mod n - In 128-Bit-Experimenten wurde ähnlich wie beim Fermat-Test in etwa 0,042 Sekunden eine Primzahl gefunden
- Die Fehlerschranke im schlimmsten Fall liegt bei Miller-Rabin bei
4^-k, für großenim Mittel eher bei8^-k - Bei
k=10ergab sich eine mittlere Fehlerwahrscheinlichkeit von0.000000000931323% - Verglichen wurde das mit der Wahrscheinlichkeit
2^-30, also 30-mal hintereinander eine Münze zu werfen und jedes Mal Kopf zu erhalten - Für echte Kryptografie muss man bei der Wahl zufälliger Basen und unter adversariellen Bedingungen vorsichtiger sein
Ein BigInt selbst bauen
- Mit Rusts eingebauten Ganzzahltypen ließen sich Zahlen jenseits von 64 Bit nicht ausreichend gut handhaben, daher wurde eine Implementierung für Ganzzahlen mit beliebiger Präzision (BigInt) nötig
- Wegen der Einschränkung, keine externe bigint-Crate zu verwenden, wurde auch BigInt selbst implementiert
-
Versuch 1: Array aus Dezimalziffern
- Zunächst wurde versucht, große Zahlen als Array aus Dezimalziffern zu speichern
- Addition und Multiplikation ließen sich wie bei schriftlichen Rechenverfahren umsetzen, an der Division scheiterte der Ansatz jedoch
-
Versuch 2: Binärarray auf Basis von bool
- Der zweite Ansatz speicherte die Zahl als Array aus 0 und 1
BigIntverwendete ein Array[bool; 2048]- Beim Multiplizieren zweier 1024-Bit-Zahlen wird Platz für bis zu 2048 Bit benötigt, daher wurden 2048 Bit vorgesehen
- Addition und Subtraktion wurden mit einem Full-Adder-Verfahren implementiert
- Die Multiplikation nutzte die Eigenschaften binärer Zahlen und arbeitete als shift-and-add
- Die Division wurde als binäre schriftliche Division umgesetzt
- Mit dieser Implementierung gelang zwar die erste 1024-Bit-Primzahl, aber die Laufzeit lag bei etwa 32 Minuten und 44,90 Sekunden
- Technisch war das Ziel erreicht, doch die Bedingung einer „vernünftigen Zeit“ wurde klar verfehlt
-
Versuch 3: Byte-Chunks
- Dann fiel auf, dass jedes
boolim bool-Array nicht 1 Bit, sondern 1 Byte belegt [bool; 2048]belegt also nicht 2048 Bit, sondern 2048 Byte- Danach wurde auf ein Schema umgestellt, bei dem 2048 Bit in einem Array von 256 Byte gespeichert werden
- Addition, Subtraktion und Multiplikation funktionierten ohne große Änderungen weiter; die Division wurde so angepasst, dass Byte-Chunks wie eine Bitliste behandelt werden
- Damit sank die Zeit zur Erzeugung einer 1024-Bit-Primzahl auf 4 Minuten 43 Sekunden
- Dann fiel auf, dass jedes
-
Versuch 4:
u64-Chunks- Der Byte-Chunk-Ansatz war letztlich ein ziffernbasiertes BigInt mit höherer Basis
- Im nächsten Schritt wurden 2048 Bit als 32
u64-Chunks gespeichert - Jeder Chunk funktionierte wie eine einzelne „Ziffer“
- Für das Produkt zweier
u64-Chunks wurdeu128genutzt - In dieser Struktur lässt sich eine 1024-Bit-Zahl nicht als 309 Dezimalstellen, sondern als 16
u64-Chunks darstellen - Die Zeit zur Erzeugung einer 1024-Bit-Primzahl verbesserte sich auf 60–90 Sekunden
Optimierung der Engpässe
- In einfachen Benchmarks war der Unterschied zwischen der binären Implementierung und der
u64-Chunk-Implementierung deutlicha + bunda - b: 5537.35ns → 123.57nsa * b: 1292283.14ns → 842.32nsa / bunda % b: 733446.76ns → 44440.12nsa < bunda > b: 2506.02ns → 58.91ns
- Die anschließenden Optimierungen konzentrierten sich vor allem auf Division, Multiplikation, interne Operationen in Miller-Rabin und die Logik zur Erzeugung von Kandidaten
-
Division
- Der größte Engpass war die Division
- Selbst mit
u64-Chunks arbeitete die vorhandene Division intern noch als schriftliche Division Bit für Bit - Unter Bezug auf den Algorithmus auf Seite 598 des Handbook of Applied Cryptography wurde eine radixbasierte schriftliche Division implementiert
- Dabei wird die aktuelle Quotienten-„Ziffer“ aus den ersten 3 „Ziffern“ des Dividend und den ersten 2 „Ziffern“ des Divisor geschätzt
- Diese Implementierung sparte pro Division etwa 40.000 ns
- Falls der Divisor aus nur einem einzigen
u64-Chunk bestand, wurde ein Spezialfall mit direkterer schriftlicher Division überu128verwendet - Gerade in Miller-Rabin tritt dieser Fall häufig auf
-
Multiplikation
- Die Multiplikation wurde etwa doppelt so schnell, indem die Schleifen so umgestellt wurden, dass kein BigInt mehr für Zwischenergebnisse nötig war
- Außerdem wurde gezählt, wie viele Chunks tatsächlich belegt sind, damit Schleifen nur über nicht nullwertige Chunks laufen
- Da BigInt meist Zahlen bis 1024 Bit enthält, bleibt oft etwa die Hälfte des 2048-Bit-Speicherraums ungenutzt
- Karatsuba- oder FFT-basierte Multiplikation wurden ebenfalls betrachtet, erschienen aber für eine Eigenimplementierung zu komplex, und die aktuelle Multiplikation war bereits schnell genug
-
Interne Optimierungen in Miller-Rabin
- In der Miller-Rabin-Implementierung lag der Fokus darauf, teure Operationen zu reduzieren
- Statt
x = mod_exp(x, 2, n)wurde direktx = (x * x) % nberechnet - Das erste
mod_exp()wurde durch eine vereinfachte Inline-Version ersetzt, um den Overhead von Funktionsaufrufen zu reduzieren - Für die Prüfung auf gerade Zahlen wurde
num.is_even()ergänzt, um% 2zu vermeiden d / 2wurde ind >>= 1geändert+= 1und-= 1wurden als Spezialfälle überincrease()unddecrease()behandelt- Gerade
is_even()undd >>= 1brachten jeweils etwa 70.000 ns Gewinn - Im finalen Benchmark wurde die optimierte
u64-Chunk-Version deutlich schneller a * b: 842.32ns → 295.04nsa / bunda % b: 44440.12ns → 831.77nsa / 2: 75121.58ns → 60.89nsa % 2 == 0: 78400.87ns → 21.65nsa - 1: 103.15ns → 67.54ns
Der finale 1024-Bit-Primzahlgenerator
- Die finale Funktion liest zunächst eine 1024-Bit-Zufallszahl aus
/dev/urandom- Das höchstwertige Bit wird gesetzt, um 1024 Bit Länge sicherzustellen
- Das niederwertigste Bit wird gesetzt, um eine ungerade Zahl sicherzustellen
- Anschließend wird nicht jedes Mal eine komplett neue Zufallszahl gelesen, sondern jeweils
2zum Kandidaten addiert, um zum nächsten ungeraden Kandidaten zu gehenincrease_by_2()führt dabei meist nur eine Addition in einem einzigenu64-Chunk aus
- Vor Miller-Rabin wird zunächst trial division mit einer Liste kleiner Primzahlen durchgeführt
- Im finalen Code werden die ersten 1000 kleinen Primzahlen verwendet
- Diese kleinen Primzahlen passen in einen einzelnen
u64-Chunk und profitieren daher vom schnellen Spezialfall der Ein-Chunk-Division
- Das Problem lässt sich als embarrassingly parallel behandeln, da weder gemeinsam genutzter Speicher noch Synchronisation zwischen Threads nötig sind
- 16 CPU-Threads suchen jeweils nach einer Primzahl, und der Wert des Threads, der zuerst ein Ergebnis liefert, wird verwendet
- Ein finales Beispiel lief mit einer elapsed time von etwa 0,086 Sekunden
- Die CPU-Auslastung wurde mit 690 % angezeigt
- Der Durchschnitt aus 100 Läufen lag bei
0.04109 ± 0.00307Sekunden- Im Mittel wurde also in rund 40 ms eine 1024-Bit-Primzahl gefunden
- Einzelne Aufrufe von
prime_1024bit()können wegen der Zufälligkeit zwischen etwa 8 ms und etwa 800 ms schwanken - Durch die parallele Ausführung und die Wahl des schnellsten Ergebnisses wird diese Schwankung abgefedert
Code und Grenzen
- Der vollständige Code und das Repository sind auf github veröffentlicht
- Diskussionslinks gibt es auf hackernews und reddit
- Diese Implementierung ist wahrscheinlich nicht für echte kryptografische Sicherheit geeignet; sie zielte auch nicht auf eine Kryptobibliothek, sondern eher auf ein Lern- und Implementierungsexperiment ab
1 Kommentare
Hacker-News-Meinungen
Es gab einige Kryptowährungen, die das Finden großer Primzahlen als Teil ihrer Proof-of-Work-Funktion nutzten, und vor etwa acht Jahren konnte man schon mit einer sehr schnellen Implementierung für Primzahltests ziemlich gut Geld verdienen.
Ich war eine Zeit lang Autor und Maintainer der Mining-Software für riecoin; warum genau, weiß ich nicht mehr, vermutlich einfach, weil ich Primzahlen mochte.
Dieser Beitrag lässt die wichtigste Optimierung für schnelle Primzahltests aus: Montgomery-Multiplikation: https://en.m.wikipedia.org/wiki/Montgomery_modular_multiplic...
Sie ist die Grundlage praktischer schneller Implementierungen modularer Exponentiation.
Niall Emmart, der damals meines Wissens in der Wissenschaft war und heute bei Nvidia ist, hat die unglaublich schnelle GPU-Big-Integer-Bibliothek CGBN veröffentlicht: https://github.com/NVlabs/CGBN
Sie ist immer noch die schnellste Implementierung für Batch-modulare Exponentiation, die ich kenne, und wenn ich kurz nerdig schwärmen darf: atemberaubend.
Irgendwann sollte ich die Geschichte aufschreiben, wie ich damit die Produktion einer kleinen Kryptowährung etwa fünf Jahre lang dominiert habe. Außerdem enthält Python mit der Drei-Argumente-Form von
pow(x, y, m)eine ziemlich gute modulare Exponentiation zur Berechnung vonx^y % m.Damit lassen sich Fermat- oder Miller-Rabin-Primzahltests sehr einfach bauen, wenn man sie selbst implementieren möchte, und das macht ziemlich Spaß. Wenn man sie nicht selbst bauen will, ist auch
mpz_probab_prime()aus der gmp-Bibliothek gut. gmp ist natürlich schneller, aber beim Herumspielen mit großen Primzahlen ist der Spaß an einem zweizeiligen Fermat-Test schwer zu übertreffen.Das ist eng mit Batch-modularer Exponentiation verwandt, arbeitet aber nicht modulo einer Primzahl, sondern auf elliptischen Kurven. Ich sehe das als Fortsetzung der CGBN-Arbeit.
Letztes Jahr hat er beim Krypto-Lunch-Seminar in Stanford einen guten Vortrag gehalten; Folien und Aufzeichnung sind online.
https://cbr.stanford.edu/seminarTalks/slides_20230526_niall_...
https://www.youtube.com/watch?v=KAWlySN7Hm8
Ich wüsste gern, ob sie nur die vage Vorstellung hatten, dass Kryptografie irgendwie Primzahlen nutzt, ohne zu wissen, wann und warum, oder ob es einen tieferen Grund gab.
pow(x,e,mod)bin ich von Perl zu Python gewechselt.Wenn es einen vorgegebenen Maximalbereich für Zahlen gibt, ist es einfach, Miller-Rabin praktisch deterministisch zu machen.
Man wählt Basen, für die bewiesen ist, dass sie alle Pseudoprimzahlen innerhalb dieses Bereichs gemeinsam aussieben.
Die Liste wird auch nicht lang. Miller-Rabin ist wirklich mächtig.
Online habe ich keine Antwort gefunden.
Eine Zeile Inline-Assembly macht die Schulbuchmultiplikation großer Ganzzahlen einfach: https://github.com/jcalvinowens/toy-rsa/blob/master/bfi.c#L4...
Wenn ich in der Zeit zurückgehen und an C nur eine Sache ändern könnte, würde ich das Konzept der erweiterten Multiplikation einbauen. Schade, dass Rust das auch nicht hat. Hardware-Unterstützung gibt es überall. Der Cortex M0 kann nicht einmal dividieren, hat aber erweiterte Multiplikation.
Der Code stammt aus einer sehr hässlichen Spielzeug-RSA-Implementierung, die ich vor langer Zeit geschrieben habe: https://github.com/jcalvinowens/toy-rsa
Der Grund, warum ein Fermat-Test allein ausreichte, war, dass der Algorithmus nicht funktioniert hätte, wenn die Primzahlen nicht tatsächlich prim gewesen wären. Der Fermat-Test ist schnell, und ein einmaliges Ver- und Entschlüsseln beseitigt auch die extrem geringe Wahrscheinlichkeit, dass eine der beiden Zahlen ein Fermat-Lügner ist.
Allerdings weiß ich nicht, ob man beweisen kann, dass es keine RSA-Schlüsselpaare gibt, die Nachrichten auch mit nicht-primen P/Q-Werten erfolgreich ver- und entschlüsseln können. In einer echten Implementierung wäre das natürlich nicht der richtige Weg, aber ich habe nie eine Antwort darauf gefunden.
In C23 wurde der Typ
_BitInt(N)hinzugefügt; zum Beispiel kann man für einen 128-Byte-Typ_BitInt(1024)verwenden.Die Compiler-Unterstützung ist allerdings begrenzt. In Clang kann man mit dem Flag
-fexperimental-max-bitint-width=Nerlauben, dass N größer als 128 ist. Wenn N größer als 128 ist und man_BitInt(N)dividiert, crasht der Compiler einfach, aber+,-,*funktionieren wie erwartet.Es gibt das Built-in
@mulWithOverflow, das zusammen mit dem Ergebnis das Overflow-Bit zurückgibt, und Integer gibt es bis(u|i)65535.Je nachdem, was man tut, kann man nach dem Erkennen eines Overflows auf einen größeren Typ anheben oder zuerst anheben und anschließend optional abschneiden.
Außerdem unterstützt Zig mit den separaten Operatoren
*|saturierende Multiplikation und mit*%Wrapping-Multiplikation. Man kann sie verwenden, wenn man genau diese Semantik braucht. Andere Overflows sind undefiniertes Verhalten, das von Safety-Checks erfasst wird; in den Build-Modi Debug und ReleaseSafe führt das zu einer Panic.pundqteilerfremde Carmichael-Zahlen sind, kann RSA Nachrichten weiterhin erfolgreich ver- und entschlüsseln.Allerdings hat
p*qdann kleinere Primfaktoren und ist leichter zu faktorisieren, was die Sicherheit senkt.Ich frage mich, wie lange diese Arbeit gedauert hat.
Ich habe in einem Forschungsprojekt im Bachelor Multiplikation großer Ganzzahlen gemacht, und das hat fast zwei Semester gedauert. Ich habe Karatsuba, Toom-Cook, komplexe FFT, einige NTTs und Schönhage-Strassen implementiert.
Primzahlen sind fast schon mathematische Magie. Für alle, die sich dafür interessieren, ist Silvermans A Friendly Introduction to Number Theory ein hervorragendes Mathebuch.
Nebenbei: Der Link auf der Seite lautet
4025051statt40250519.Guter Artikel. Ich habe vor Kurzem selbst ein wenig Big-Integer-Code für eine frühe Version von [0] geschrieben, und ich erinnere mich, wie frustrierend es ist, die abstrakten Beschreibungen in mathematischen Papers in tatsächliche Operationen zu übersetzen.
Allerdings habe ich einen kleinen Einwand:
Wenn man den gesamten Bereich von
u64nutzt, sind die Zahlen nicht zur Basis2^64-1, sondern zur Basis 2^64 dargestellt. Jedes Word hat einen Bereich von 0 bis2^64-1, genauso wie jede Dezimalziffer von 0 bis 9 geht.[0] https://github.com/LegionMammal978/bigfoot-sim
Wenn man wie bei der letzten Optimierung im Fehlerfall keine neue Zufallszahl erzeugt, sondern die Zahl jeweils um 2 erhöht, schwächt das die Sicherheit ein wenig.
Da Primzahlen nicht gleichmäßig verteilt sind, entsteht eine Verzerrung zugunsten von Primzahlen direkt nach großen Primzahllücken.
Es ist ein Kompromiss zwischen Ausführungsgeschwindigkeit und Zufälligkeit der Primzahl; ich habe mich für Geschwindigkeit entschieden, weil 16 Threads jeweils bei einer Zufallszahl starten und darum konkurrieren, eine Primzahl zu finden, was meiner Ansicht nach genug zusätzliche Zufälligkeit bringt.
Wenn man Zufälligkeit stärker gewichtet als Geschwindigkeit, ist es eine einfache Änderung,
+=2durch einenrng()-Aufruf zu ersetzen.Guter und gut geschriebener Artikel.
Der Autor meint vermutlich base-256, nicht
base-255.Ein paar 1–2-KB-Zahlen passen problemlos in den L1-Cache, und selbst wenn nicht, gibt es mehr als ein Megabyte L2-Cache mit etwa 3 ns Zugriffszeit.
Im Artikel heißt es, man habe wegen L1-Cache-Misses vermutlich auf RAM-Lese-/Schreibzugriffe gewartet, aber darauf wird später nicht mehr zurückgekommen.
Außerdem geht es hier nur um die Primzahlerzeugung, man vermeidet also die meisten Fallstricke von RSA, und
urandomsollte sicher sein. Wenn der Code korrekt funktioniert, gibt es nicht allzu viel, was gravierend schiefgehen kann.Bei RSA gibt es einige Themen rund um schwache Primzahlen, die man vermeiden sollte, aber ich weiß nicht, ob sie hier häufig genug auftreten, um praktisch ein Problem zu sein.
Das erinnert mich an ein Projekt im ersten Studienjahr vor einigen Jahrzehnten.
Mein Projektpartner und Freund, der später Jahrgangsbester wurde, hatte die Idee und implementierte die zentrale Mathematik: eine 4096-Bit-RSA-Verschlüsselung zu bauen.
Ich erinnere mich, wie langsam die Primzahlerzeugung in der finalen Implementierung war. Auf einer PA-RISC-Workstation dauerte die Erzeugung etwa 20 Minuten.
Mein mathematikbegeisterter Freund optimierte den Code auch nach Projektende weiter, und ich erinnere mich, wie er Papers zu Primzahltests und Big-Integer-Mathematik las.
Zum Beispiel brachte es eine enorme Verbesserung, bei der Multiplikation von Komponenten die Multiplikation zu überspringen und das Ergebnis als 0 zu setzen, wenn einer der Werte 0 war.
Sonst wartet man entweder lange oder opfert Sicherheit, die auch in Zukunft noch Bestand haben soll.
Das Setzen des niedrigen Bits auf 1 verstehe ich. Gerade Zahlen sind niemals prim, natürlich mit Ausnahme von 2.
Aber ich verstehe nicht, warum auch das hohe Bit auf 1 gesetzt wird. Ich kenne mich mit Primzahlen oder Kryptografie nicht besonders aus, aber es sieht so aus, als würde man unnötig 1 Bit Entropie aufgeben. Was übersehe ich?
Byte-Kodierungen variabler Länge können beim Datenaustausch zwischen unterschiedlicher Software Probleme verursachen, wenn die Spezifikation nicht sehr klar und gut getestet ist.
Siehe die Probleme bei RSA-basiertem DHE, wenn der öffentliche Serverschlüssel führende Nullen enthält.
Wenn die erste Ziffer 0 ist, ist es keine zweistellige Zahl.
Ergänzend dazu: Bei RSA multipliziert man zwei Primzahlen. Wenn eine davon 1024 Bit hat, kann die andere, wenn ich mich richtig erinnere, etwa 200 Bit haben und trotzdem die für den Schlüssel nötige Anzahl an Entropie-Bits erreichen.
Wenn man also beide Primzahlen auf 1024 Bit setzt, hat man sogar etwas Spielraum.
Das scheint sicherer zu sein, als sich zu fragen, ob eine 1020-Bit-Primzahl auch in Ordnung ist, wenn jemand eine 1024-Bit-Primzahl verlangt hat. So wie man
00042normalerweise nicht als fünfstellige Zahl betrachtet.Technisch kann die optimale Wahl davon abhängen, wofür genau sie verwendet wird, aber die Vorgehensweise im Artikel wirkt wie die sicherere Standardeinstellung.