Einmalpasswörter (OTPs) werden häufig zu Authentifizierungs- und Verifizierungszwecken in verschiedenen Anwendungen und Diensten verwendet. Normalerweise werden sie von einem Server generiert und per SMS, E-Mail oder über andere Kanäle an den Benutzer gesendet. Der Benutzer gibt dann das OTP ein, um seine Identität zu bestätigen oder eine Aktion auszuführen.
Ich bekam eine Aufgabe, bei der wir eine OTP-basierte Verifizierung in Node JS implementieren mussten. Ich bin mir sicher, dass die meisten von uns Entwicklern/Ingenieuren vor der Integration so etwas im Internet nach Best Practices, Tutorials, aktuellen technischen Trends und Problemen suchen, mit denen andere große Softwaresysteme bei ihrer Implementierung konfrontiert sind. Also habe ich es gemacht, und das, was meine größte Aufmerksamkeit erregte, waren Bibliotheken wie otp-lib und otp-generator, deren einzige Funktion darin bestand, ein OTP zu generieren. Die restlichen Aufgaben wie das Versenden per SMS oder E-Mail müssen noch auf andere Weise erledigt werden. Die erste Frage, die einem in den Sinn kommt, nachdem man weiß, dass es solche Bibliotheken gibt, ist, warum wir so viel Aufwand betreiben müssen, um eine Bibliothek zum Generieren von OTP zu verwenden, wenn wir doch nur einen Einzeiler schreiben müssen:
const otp = Math.ceil(Math.random() * 10000)
In diesem Blogbeitrag erkläre ich, was ich bei unserer kleinen Recherche nach OTP-Generatoren gelernt habe, warum die Verwendung von Math.random() zum Generieren von OTPs eine schlechte Idee ist, welche anderen Möglichkeiten es gibt, ein OTP zu generieren und warum eine Bibliothek dies tun sollte für eine solche Aufgabe verwendet?
Es gibt hauptsächlich zwei Arten von Zufallszahlen:
Pseudozufallszahlen werden von einem Algorithmus generiert, der einen Anfangswert, einen sogenannten Startwert, annimmt und eine Folge von Zahlen erzeugt, die zufällig erscheinen. Allerdings ist der Algorithmus deterministisch, was bedeutet, dass Sie die nächste Zahl in der Folge vorhersagen können, wenn Sie den Startwert und den Algorithmus kennen. Math.random() von Javascript und random.randInt() von Python sind Beispiele für einen Pseudozufallszahlengenerator.
Kryptografische Zufallszahlen werden durch einen Prozess generiert, der unvorhersehbar ist und nicht reproduziert oder erraten werden kann. Sie basieren normalerweise auf einem physikalischen Phänomen, wie zum Beispiel atmosphärischem Rauschen, thermischem Rauschen oder Quanteneffekten.
Verschiedene Javascript-Engines verhalten sich bei der Generierung einer Zufallszahl etwas unterschiedlich, aber im Wesentlichen läuft alles auf einen einzigen Algorithmus XorShift128+ hinaus.
XorShift ist ein deterministischer Algorithmus, der die Addition als schnellere nichtlineare Transformationslösung verwendet. Im Vergleich zu seinen Pendants, die Multiplikation verwenden, ist dieser Algorithmus schneller. Es besteht auch eine geringere Ausfallwahrscheinlichkeit als Mersenne Twister (wird vom Zufallsmodul von Python verwendet)
Der Algorithmus nimmt zwei Zustandsvariablen auf, wendet XOR und Verschiebung darauf an und gibt die Summe der aktualisierten Zustandsvariablen zurück, die eine Ganzzahl ist. Die Zustände werden im Allgemeinen mithilfe der Systemuhr gesetzt, da diese eine gute Quelle für eine eindeutige Nummer ist.
Eine Implementierung von XOR Shift Plus in Javascript sieht so aus:
let state0 = 1; let state1 = 2; function xorShiftPlus() { let s1 = state0; let s0 = state1; state0 = s0; s1 ^= s1 << 23; s1 ^= s1 >> 17; s1 ^= s0; s1 ^= s0 >> 26; state1 = s1; return state0 + state1; }
Die zurückgegebene Ganzzahl wird mithilfe einer ODER-Verknüpfung mit einer Konstante in ein Double umgewandelt. Die detaillierte Implementierung finden Sie im Chrome-Quellcode.
Das Ergebnis von Math.random() vorherzusagen ist schwierig, aber nicht völlig unmöglich. Wenn Sie den Algorithmus kennen, können Sie problemlos dieselben Zufallszahlen neu generieren, wenn Sie die Werte von state0 und state1 kennen.
Reverse Engineering XorShift128+ Mithilfe eines Z3-Theorembeweisers können Sie den Wert von Zustand0 und Zustand1 ermitteln, indem Sie drei aufeinanderfolgende, von einem Server generierte Zufallszahlen bereitstellen.
Die Implementierung des Z3-Lösers finden Sie hier.
Jetzt stellt sich die Frage, wie man diese 3 Zufallszahlen von einem Server erhält. Das ist der schwierige Teil und kann in einigen der folgenden Fälle erreicht werden:
Another approach to exploit a random number is using the fact that Math.random() only returns numbers between 0 and 1 with 16 decimal places. This means that there are only 10^16 possible values that Math.random() can return. This is a very small space compared to the space of possible OTPs. if your OTP has 6 digits, there are 10^6 possible values. This visualizer shows that there is a pattern to the numbers generated. Using it, the possibilities can be reduced by 30%. Therefore, if you can guess or brute-force some of the digits of the OTP, you can reduce the space of possible values and increase your chances of finding the correct OTP.
As mentioned previously, cryptographic random numbers are non-deterministic because they depend on the physical factors of a system. Every programming language can access those factors using low-level OS kernel calls.
NodeJS provides its inbuilt crypto module, which we can use to generate randomBytes and then convert them to a number. These random bytes are cryptographic and purely random in nature. The generated number can easily be truncated to the exact number of digits we want in OTP.
import * as crypto from 'crypto'; const num = parseInt(crypto.randomBytes(3).toString('hex'), 16) // num.toString().slice(0,4) // truncate to 4 digits
NodeJS 14.10+ provides another function from crypto to generate a random number in a given min-max range.
crypto.randomInt(1001, 9999)
Even after knowing the vulnerability of Math.random() and finding a more secure way to generate a random number cryptographically, we still remain with the same question from the beginning. Why do we have to go to such lengths to use a library to generate OTP when all we have to do is write a one-liner?
Before answering this questions, let's take a look at what is the inconvenience faced while handling and storing an OTP. The problem with using the above method to generate OTPs is that you have to store them in the database in order to verify them later. Storing the OTP in the database is not a good practice for the following reasons:
The OTP libraries use different algorithms and techniques to generate and verify OTPs that behave similarly to a Cryptographic random OTP, while also removing the overhead to store the OTP in a database.
There are mainly two types of OTP implementation techniques.
HOTP stands for HMAC-based One-Time Password. It is an algorithm that generates an OTP based on a secret key and a counter. The secret key is a random string that is shared between the server and the user. The counter is an integer that increments every time an OTP is generated or verified.
The algorithm works as follows:
• The server and the user generate the same OTP by applying a cryptographic hash function, such as SHA-1, to the concatenation of the secret key and the counter.
• The server and the user truncate the hash value to obtain a fixed-length OTP, usually 6 or 8 digits.
• The user sends the OTP to the server for verification.
• The server compares the OTP with its own generated OTP and verifies it if they match.
• The server and the user increment their counters by one.
HOTP wird hauptsächlich bei der Hardware-Token-basierten Authentifizierung wie Yubikey verwendet. Yubikey ist im Grunde ein programmierter Hardwareschlüssel, den Sie physisch an Ihren Computer oder Ihr Telefon anschließen können. Anstatt einen Code per SMS oder E-Mail zu erhalten, können Sie auf Yubikey einfach eine Taste drücken, um sich zu verifizieren und zu authentifizieren.
• Das Speichern des OTP in der Datenbank ist nicht erforderlich, da es im laufenden Betrieb generiert und überprüft werden kann.
• Es basiert nicht auf Pseudozufallszahlen, da es eine kryptografische Hash-Funktion verwendet, die unvorhersehbar und irreversibel ist.
• Es ist resistent gegen Replay-Angriffe, da jedes OTP nur einmal gültig ist.
• Es erfordert eine Synchronisierung zwischen dem Server und den Zählern des Benutzers. Wenn sie aufgrund von Netzwerkverzögerungen, Übertragungsfehlern oder Geräteverlust nicht synchron sind, schlägt die Überprüfung fehl.
• Es bleibt gültig, solange kein neu generiertes HOTP verwendet wird, das kann eine Sicherheitslücke darstellen.
• Es erfordert eine sichere Möglichkeit, die geheimen Schlüssel zu verteilen und zu speichern. Wenn die geheimen Schlüssel durchsickern oder gestohlen werden, können die OTPs kompromittiert werden.
TOTP steht für Time-based One-Time Password. Es handelt sich um einen Algorithmus, der ein OTP basierend auf einem geheimen Schlüssel, einem Zeitstempel und einer Epoche generiert.
Der Algorithmus funktioniert wie folgt:
• Der Server legt einen geheimen Schlüssel für den Benutzer fest und teilt ihn über ein Medium wie Authenticator-Apps.
• Der Server kann direkt ein OTP generieren und es per E-Mail oder SMS an den Benutzer senden oder den Benutzer auffordern, einen Authentifikator zu verwenden, um mithilfe des gemeinsamen Schlüssels ein OTP zu generieren.
• Der Nutzer kann das erhaltene OTP direkt per Mail oder SMS versenden oder es im Falle von 2FA in einem festen Zeitfenster in der Authenticator-App generieren.
• Der Server vergleicht das OTP mit seinem eigenen generierten OTP und überprüft, ob sie im Epochenzeitbereich nahe genug liegen.
• Das Speichern des OTP in der Datenbank ist nicht erforderlich, da es im laufenden Betrieb generiert und überprüft werden kann.
• Es basiert nicht auf Pseudozufallszahlen, da es eine kryptografische Hash-Funktion verwendet, die unvorhersehbar und irreversibel ist.
• Es ist resistent gegen Replay-Angriffe, da jedes OTP nur für einen kurzen Zeitraum gültig ist.
• Es ist keine Synchronisierung zwischen dem Server und den Zeitstempeln des Benutzers erforderlich. Solange sie über einigermaßen genaue Uhren verfügen, können sie OTPs unabhängig generieren und überprüfen.
• Es erfordert eine sichere Möglichkeit, die geheimen Schlüssel zu verteilen und zu speichern. Wenn die geheimen Schlüssel durchsickern oder gestohlen werden, können die OTPs kompromittiert werden.
• Es erfordert eine zuverlässige Zeitquelle sowohl für den Server als auch für den Benutzer. Wenn ihre Uhren falsch eingestellt oder manipuliert sind, schlägt die Überprüfung fehl.
• Der Server muss die Zeitverschiebung oder Verzögerung bei der Verarbeitung der Anforderungen berücksichtigen, daher sollte er eine etwas längere Epoche beibehalten als der Client.
Durch unsere kleine Forschungsreise zum OTP haben wir erfahren, dass Math.random() vorhergesagt, ausgenutzt und wiedergegeben werden kann. Wir haben auch erfahren, dass das Speichern von OTPs in der Datenbank keine gute Praxis ist.
TOTP kann sichere und effiziente OTPs generieren und diese auch überprüfen. Es kann sowohl offline als auch online ein OTP generieren, erfordert keine Synchronisierung oder Speicherung und ist resistent gegen Replay-Angriffe. Somit löst es die meisten unserer Bedenken in Bezug auf Best Practices, Sicherheit und Zuverlässigkeit.
Das obige ist der detaillierte Inhalt vonWarum verwenden wir OTP-Bibliotheken, wenn wir einfach Math.random() ausführen können?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!