Les mots de passe à usage unique (OTP) sont largement utilisés à des fins d'authentification et de vérification dans diverses applications et services. Un serveur les génère généralement et les envoie à l'utilisateur par SMS, e-mail ou autres canaux. L'utilisateur saisit ensuite l'OTP pour confirmer son identité ou effectuer une action.
J'ai eu une tâche dans laquelle nous devions implémenter la vérification basée sur OTP dans Node JS. Avant d'intégrer quelque chose comme ça, je suis sûr que la plupart d'entre nous, développeurs/ingénieurs, recherchons sur Internet les meilleures pratiques, les didacticiels, les tendances techniques récentes et les problèmes auxquels d'autres systèmes logiciels majeurs sont confrontés lors de leur implémentation. Alors je l'ai fait, et ce qui a le plus attiré mon attention, ce sont des bibliothèques comme otp-lib et otp-generator, dont la seule fonction était de générer un OTP. Le reste des tâches, comme l'envoi par SMS ou par e-mail, doit encore être effectué par d'autres moyens. La première question qui nous vient à l'esprit après avoir su que de telles bibliothèques existent est pourquoi nous devons faire autant d'efforts pour utiliser une bibliothèque pour générer des OTP alors que tout ce que nous avons à faire est d'écrire une seule ligne :
const otp = Math.ceil(Math.random() * 10000)
Dans cet article de blog, j'expliquerai ce que j'ai appris lors de notre petite recherche sur les générateurs OTP, pourquoi utiliser Math.random() pour générer des OTP est une mauvaise idée, quelles sont les autres façons de générer un OTP et pourquoi une bibliothèque devrait être utilisé pour une telle tâche ?
Il existe principalement deux types de nombres aléatoires :
Les nombres pseudo-aléatoires sont générés par un algorithme qui prend une valeur initiale, appelée graine, et produit une séquence de nombres qui semblent aléatoires. Cependant, l’algorithme est déterministe, ce qui signifie que si vous connaissez la graine et l’algorithme, vous pouvez prédire le prochain numéro de la séquence. Math.random() de Javascript et random.randInt() de Python sont un exemple de générateur de nombres pseudo-aléatoires.
Les nombres aléatoires cryptographiques sont générés par un processus imprévisible et ne peuvent être reproduits ou devinés. Ils sont généralement basés sur un phénomène physique, tel que le bruit atmosphérique, le bruit thermique ou les effets quantiques.
Différents moteurs Javascript se comportent un peu différemment lors de la génération d'un nombre aléatoire, mais tout se résume essentiellement à un seul algorithme XorShift128+.
XorShift est un algorithme déterministe, qui utilise l'addition comme solution de transformation non linéaire plus rapide. Comparé à ses pairs, qui utilisent la multiplication, cet algorithme est plus rapide. Il a également moins de chances d'échec que Mersenne Twister (utilisé par le module aléatoire de Python)
L'algorithme prend en compte deux variables d'état, leur applique un XOR et un décalage, et renvoie la somme des variables d'état mises à jour qui est un nombre entier. Les états sont généralement générés à l'aide de l'horloge système car c'est une bonne source pour un numéro unique.
Une implémentation de XOR shift plus en javascript ressemble à ceci :
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; }
L'entier renvoyé est converti en double à l'aide de l'opération OU avec une constante. Vous pouvez trouver l'implémentation détaillée sur le code source de Chrome.
Prédire le résultat de Math.random() est difficile, mais ce n'est pas complètement impossible. Connaissant l'algorithme, vous pouvez facilement régénérer les mêmes nombres aléatoires si vous connaissez les valeurs de state0 et state1.
Ingénierie inverse XorShift128+ À l'aide d'un prouveur de théorème Z3, vous pouvez trouver la valeur de state0 et state1 en fournissant 3 nombres aléatoires consécutifs générés par un serveur.
L'implémentation du solveur Z3 peut être trouvée ici.
Maintenant, la question se pose de savoir comment obtenir ces 3 nombres aléatoires à partir d'un serveur. C'est la partie la plus difficile, et peut être obtenue dans certains des cas suivants :
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 est principalement utilisé dans l'authentification matérielle basée sur des jetons comme Yubikey. Yubikey est essentiellement une clé matérielle programmée que vous pouvez connecter physiquement à votre ordinateur ou téléphone. Au lieu de recevoir un code par SMS ou par e-mail, vous pouvez simplement appuyer sur un bouton de Yubikey pour vous vérifier et vous authentifier.
• Il ne nécessite pas de stocker l'OTP dans la base de données, car il peut être généré et vérifié à la volée.
• Il ne repose pas sur des nombres pseudo-aléatoires, car il utilise une fonction de hachage cryptographique imprévisible et irréversible.
• Il résiste aux attaques par rejeu, car chaque OTP n'est valide qu'une seule fois.
• Cela nécessite une synchronisation entre le serveur et les compteurs de l'utilisateur. S'ils sont désynchronisés en raison de retards du réseau, d'erreurs de transmission ou de perte de l'appareil, la vérification échouera.
• Il reste valide tant qu'un HOTP nouvellement généré n'est pas utilisé, cela peut constituer une vulnérabilité.
• Cela nécessite un moyen sécurisé de distribuer et de stocker les clés secrètes. Si les clés secrètes sont divulguées ou volées, les OTP peuvent être compromis.
TOTP signifie Time-based One-Time Password. Il s'agit d'un algorithme qui génère un OTP basé sur une clé secrète, un horodatage et une époque.
L'algorithme fonctionne comme suit :
• Le serveur décide d'une clé secrète pour l'utilisateur et la partage sur un support tel que les applications Authenticator.
• Le serveur peut générer directement un OTP et l'envoyer à l'utilisateur par mail ou SMS, ou il peut demander à l'utilisateur d'utiliser un authentificateur pour générer un OTP à l'aide de la clé partagée.
• L'utilisateur peut envoyer directement l'OTP reçu par mail ou SMS ou le générer dans l'application d'authentification en cas de 2FA dans une fenêtre de temps fixe.
• Le serveur compare l'OTP avec son propre OTP généré et le vérifie s'ils sont suffisamment proches dans la plage horaire.
• Il ne nécessite pas de stocker l'OTP dans la base de données, car il peut être généré et vérifié à la volée.
• Il ne repose pas sur des nombres pseudo-aléatoires, car il utilise une fonction de hachage cryptographique imprévisible et irréversible.
• Il résiste aux attaques par rejeu, car chaque OTP n'est valide que pour une courte période de temps.
• Il ne nécessite pas de synchronisation entre le serveur et les horodatages de l'utilisateur. Tant qu'ils disposent d'horloges raisonnablement précises, ils peuvent générer et vérifier les OTP de manière indépendante.
• Cela nécessite un moyen sécurisé de distribuer et de stocker les clés secrètes. Si les clés secrètes sont divulguées ou volées, les OTP peuvent être compromis.
• Cela nécessite une source de temps fiable à la fois pour le serveur et pour l'utilisateur. Si leurs horloges sont faussées ou falsifiées, la vérification échouera.
• Le serveur doit tenir compte de la dérive temporelle ou du retard dans le traitement des requêtes, il doit donc maintenir une époque légèrement supérieure à celle du client.
Grâce à notre petit voyage de recherche sur l'OTP, nous avons appris que Math.random() peut être prédit, exploité et rejoué. Nous avons également appris que stocker les OTP dans la base de données n'est pas une bonne pratique.
TOTP peut générer des OTP sécurisés et efficaces, et peut également les vérifier. Il peut générer un OTP hors ligne comme en ligne, ne nécessite ni synchronisation ni stockage et résiste aux attaques par relecture. Cela résout ainsi la plupart de nos préoccupations liées aux meilleures pratiques, à la sécurité et à la fiabilité.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!