Maison > développement back-end > Golang > Démystifier les OTP : la logique derrière la génération de jetons hors ligne

Démystifier les OTP : la logique derrière la génération de jetons hors ligne

Mary-Kate Olsen
Libérer: 2024-12-12 22:23:14
original
370 Les gens l'ont consulté

Bonjour ! Un autre soir, en rentrant chez moi, j'ai décidé de consulter ma boîte aux lettres. Je ne parle pas de ma boîte de réception e-mail, mais de la boîte à l'ancienne où le facteur met les lettres physiques. Et à ma grande surprise, j'y ai trouvé une enveloppe avec quelque chose à l'intérieur ! En l'ouvrant, j'ai passé quelques instants à espérer qu'il s'agissait de la lettre de Poudlard retardée de plusieurs décennies. Mais ensuite j'ai dû revenir sur Terre, une fois que j'ai remarqué qu'il s'agissait d'une lettre ennuyeuse « d'adulte » de la banque. J'ai parcouru le texte et réalisé que ma banque « 100 % numérique » pour cool kids avait été rachetée par le plus gros acteur du marché local. Et en signe d'un nouveau départ, ils ont ajouté ceci à l'enveloppe :

Demystifying OTPs: the logic behind the offline generation of tokens

À côté des instructions sur la façon de l'utiliser.

Si vous êtes comme moi et que vous n'avez jamais découvert une telle innovation technologique, permettez-moi de partager ce que j'ai appris de la lettre : les nouveaux propriétaires ont décidé d'appliquer les politiques de sécurité de leur entreprise, ce qui signifie que tous les utilisateurs les comptes auront désormais le MFA activé (félicitations pour cela, d'ailleurs). Et l'appareil que vous pouvez voir ci-dessus génère des jetons uniques à 6 chiffres qui sont utilisés comme deuxième facteur lors de la connexion à votre compte bancaire. Fondamentalement, de la même manière que des applications comme Authy, Google Authenticator ou 2FAS, mais sous une forme physique.

J'ai donc essayé et le processus de connexion s'est déroulé sans problème : l'appareil m'a montré un code à 6 chiffres, je l'ai saisi dans mon application bancaire, et cela m'a permis d'accéder. Hourra ! Et puis quelque chose m’a frappé : comment ça marche ? Il n'est pas possible qu'il soit connecté à Internet d'une manière ou d'une autre, mais il parvient à générer des codes corrects qui sont acceptés par le serveur de ma banque. Hm... Pourrait-il contenir une carte SIM ou quelque chose de similaire à l'intérieur ? Pas question !

Réalisant que ma vie ne sera plus jamais la même, j'ai commencé à m'interroger sur les applications que j'ai mentionnées ci-dessus (Authy et amis) ? Mon chercheur intérieur a été réveillé, j'ai donc mis mon téléphone en mode avion et, à ma grande surprise, j'ai réalisé qu'ils fonctionnent parfaitement hors ligne : ils continuent de générer des codes qui sont acceptés par les serveurs des applications. Intéressant !

Je ne suis pas sûr pour vous, mais j'ai toujours pris le flux de jetons unique pour acquis et je n'y ai jamais vraiment réfléchi (surtout en raison du fait que de nos jours, il est rare que mon téléphone n'ait pas Internet à moins que Je fais des aventures en plein air), c'est donc la cause première de ma surprise. Sinon, il est tout à fait logique du point de vue de la sécurité de fonctionner de cette manière, car le processus de génération est purement local, donc à l'abri des acteurs externes. Mais comment ça marche ?

Eh bien, les technologies modernes comme Google ou ChatGPT permettent de trouver facilement la réponse. Mais ce problème technique m'a semblé amusant, alors j'ai décidé de l'essayer et de le résoudre par moi-même d'abord.

Exigences

Commençons par ce que nous avons :

  • un appareil hors ligne qui génère des codes à 6 chiffres
  • un serveur qui accepte ces codes, les valide et donne un signal vert s'ils sont corrects

La partie validation du serveur laisse entendre que le serveur doit être capable de générer le même code que l'appareil hors ligne pour les comparer. Hm..ça peut être utile.

Mes observations ultérieures de mon nouveau "jouet" m'ont apporté encore plus de découvertes :

  • Si je l'éteins puis l'éteins, je peux généralement voir le même code qu'avant
  • Cependant, de temps en temps, cela change

La seule explication logique que j'ai pu trouver est que ces codes ont une certaine durée de vie. J'aimerais raconter une histoire de moi essayant de compter la durée à la manière "1-2-3-...-N", mais ce ne sera pas vrai : j'ai reçu un gros indice des applications comme Authy and Co, où j'ai vu le TTL de 30 secondes. Bonne trouvaille, ajoutons ceci à la liste des faits connus.

Résumons les exigences que nous avons jusqu'à présent :

  • Génération de code prévisible (plutôt qu'aléatoire) au format 6 chiffres
  • la logique de génération doit être reproductible, ce qui permet d'obtenir le même résultat quelle que soit la plateforme
  • la durée de vie du code est de 30 secondes, ce qui signifie que dans ce laps de temps l'algorithme de génération produit la même valeur

La grande question

Très bien, mais la question principale reste sans réponse : comment se fait-il que l'application hors ligne puisse générer la valeur qui correspondra à celle de l'autre application ? Qu’ont-ils en commun ?

Si vous êtes dans l'univers du Seigneur des Anneaux, vous vous souviendrez peut-être de la façon dont Bilbo a joué à un jeu d'énigmes avec Gollum et a réussi à résoudre celui-ci :

Cette chose que tout dévore :
Oiseaux, bêtes, arbres, fleurs ;
Ronge le fer, mord l'acier ;
Broie les pierres dures en farine ;
Tue le roi, ruine la ville,
Et bat la haute montagne.

Alerte spoiler, mais M. Baggins a eu de la chance et a accidentellement trouvé la bonne réponse : "Il est temps !". Croyez-le ou non, mais c'est exactement la réponse à notre énigme : 2 applications (ou plus) ont accès à la même heure tant qu'elles ont une horloge intégrée. Ce dernier ne pose plus de problème de nos jours, et l’appareil en question est suffisamment grand pour l’accueillir. Regardez autour de vous et il y a de fortes chances que l’heure sur votre montre, votre téléphone portable, votre téléviseur, votre four et votre horloge murale soit la même. Nous sommes dans quelque chose ici, il semble que nous ayons trouvé la base de l'informatique OTP (mot de passe à usage unique) !

Défis

Compter sur le temps comporte son propre ensemble de défis :

  • Fuseau horaire - lequel utiliser ?
  • les horloges ont tendance à se désynchroniser, et c'est un gros défi dans les systèmes distribués

Abordons-les un par un :

  • fuseau horaire : la solution la plus simple ici est de s'assurer que tous les appareils reposent sur le même fuseau horaire, et UTC peut être un bon candidat indépendant de l'emplacement
  • en ce qui concerne les horloges qui se désynchronisent : en fait, nous n'aurons peut-être même pas besoin de le résoudre, mais plutôt de l'accepter comme quelque chose d'inévitable, tant que la dérive se situe dans la seconde ou deux, ce qui pourrait être tolérable compte tenu du TTL de 30 secondes. Le fabricant du matériel de l'appareil devrait être en mesure de prédire quand une telle dérive sera atteinte, de sorte que l'appareil l'utilisera comme date d'expiration et que la banque les remplacera simplement par les nouveaux, ou aura un moyen de les connecter. au réseau pour calibrer l’horloge. Du moins, c'est ce que je pense ici.

Mise en œuvre

Ok, c'est réglé, essayons donc d'implémenter la toute première version de notre algorithme en utilisant le temps comme base. Puisque nous sommes intéressés par un résultat à 6 chiffres, il semble judicieux de s'appuyer sur un horodatage plutôt que sur une date lisible par l'homme. Commençons par là :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Selon la documentation Go, le .Unix() renvoie

le nombre de secondes écoulées depuis le 1er janvier 1970 UTC.

Voici ce qui est imprimé sur le terminal :

Current timestamp:  1733691162
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

C'est un bon début, mais si nous réexécutons ce code, la valeur de l'horodatage changera, alors que nous aimerions la garder stable pendant 30 secondes. Eh bien, c'est un jeu d'enfant, divisons-le par 30 et utilisons cette valeur comme base :

// gets current timestamp
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds
base := current / 30
fmt.Println("Base: ", base)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Exécutons :

Current timestamp:  1733691545
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et encore :

Current timestamp:  1733691552
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La valeur de base reste la même. Attendons un peu et réexécutons :

Current timestamp:  1733691571
Base:  57789719
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La valeur de base a changé, une fois la fenêtre de 30 secondes écoulée - bon travail !

Si la logique « diviser par 30 » n'a pas de sens, laissez-moi l'expliquer avec un exemple simple :

  • imaginez que notre horodatage renvoie 1
  • Si on divise 1 par 30, le résultat sera 0, car lorsque l'on utilise un langage de programmation strictement typé, diviser un entier par un entier renvoie un autre entier, qui ne se soucie pas de la partie flottante
  • cela signifie que pendant les 30 prochaines secondes, nous obtiendrons 0 alors que l'horodatage est compris entre 0 et 29
  • Une fois que l'horodatage atteint la valeur 30, le résultat de la division est 1, jusqu'à 60 (où il devient 2), et ainsi de suite

J'espère que cela a plus de sens maintenant.

Cependant, toutes les exigences ne sont pas encore satisfaites, car nous avons besoin d'un résultat à 6 chiffres, alors que la base actuelle a 8 chiffres à partir d'aujourd'hui, mais à un moment donné dans le futur, elle pourrait atteindre 9 chiffres, et ainsi de suite. . Eh bien, utilisons une autre astuce mathématique simple : divisons la base par 1 000 000 et obtenons le reste, qui aura toujours exactement 6 chiffres, car le rappel peut être n'importe quel nombre de 0 à 999 999, mais jamais plus grand :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La partie fmt.Sprintf(" d", code) ajoute des zéros non significatifs au cas où notre valeur de code aurait moins de 6 chiffres. Par exemple, 1234 sera converti en 001234.
Le code complet de cet article peut être trouvé ici.

Exécutons ce code :

Current timestamp:  1733691162
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Très bien, nous obtenons notre code à 6 chiffres, hourra ! Mais quelque chose ne va pas ici, n'est-ce pas ? Si je vous donne ce code et que vous l'exécutez en même temps que moi, vous obtiendrez le même code que moi. Cela n'en fait pas un mot de passe sécurisé à usage unique, n'est-ce pas ? Voici une nouvelle exigence :

  • le résultat devrait être différent selon les utilisateurs

Bien sûr, certaines collisions sont inévitables si nous avons plus d'un million d'utilisateurs, car il s'agit du maximum de valeurs uniques possibles pour 6 chiffres. Mais ce sont des collisions rares et techniquement inévitables, et non un défaut de conception d’algorithme comme celui que nous avons actuellement.

Je ne pense pas que des astuces mathématiques intelligentes nous aideront ici à elles seules : si nous avons besoin de résultats séparés par utilisateur, nous avons besoin d'un état spécifique à l'utilisateur pour y parvenir. En tant qu'ingénieurs et, en même temps, utilisateurs de nombreux services, nous savons que pour accorder l'accès à leurs API, les services s'appuient sur des clés privées, uniques par utilisateur. Introduisons également une clé privée pour notre cas d'utilisation afin de distinguer les utilisateurs.

Clé privée

Une logique simple pour générer des clés privées sous forme d'entiers compris entre 1 000 000 et 999 999 999 :

// gets current timestamp
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds
base := current / 30
fmt.Println("Base: ", base)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous utilisons la carte pkDb comme moyen d'éviter les doublons entre les clés privées, et si le doublon a été détecté, nous exécutons à nouveau la logique de génération jusqu'à ce que nous obtenions un résultat unique.

Exécutons ce code pour obtenir l'exemple de clé privée :

Current timestamp:  1733691545
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Utilisons cette clé privée dans notre logique de génération de code pour nous assurer que nous obtenons des résultats différents par clé privée. Puisque notre clé privée est de type entier, la chose la plus simple que nous puissions faire est de l'ajouter à la valeur de base et de conserver l'algorithme restant tel quel :

Current timestamp:  1733691552
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Assurons-nous qu'il produit des résultats différents pour différentes clés privées :

Current timestamp:  1733691571
Base:  57789719
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le résultat semble tel que nous le souhaitions et l'attendions :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds:
base := current / 30
fmt.Println("Base: ", base)

// makes sure it has only 6 digits:
code := base % 1_000_000

// adds leading zeros if necessary:
formattedCode := fmt.Sprintf("%06d", code)
fmt.Println("Code: ", formattedCode)
Copier après la connexion
Copier après la connexion
Copier après la connexion

Fonctionne à merveille ! Cela signifie que la clé privée doit être injectée dans l'appareil qui génère les codes avant d'être envoyée aux utilisateurs comme moi : cela ne devrait pas poser de problème du tout pour les banques.

Avons-nous terminé maintenant ? Enfin, seulement si nous sommes satisfaits du scénario artificiel que nous avons utilisé. Si vous avez déjà activé le MFA pour des services/sites Web sur lesquels vous avez un compte, vous avez peut-être remarqué que la ressource Web vous demande de scanner le code QR avec l'application de deuxième facteur de votre choix (Authy, Google Authenticator, 2FAS, etc. ) qui saisira le code secret dans votre application et commencera à générer un code à 6 chiffres à partir de ce moment. Alternativement, il est possible de saisir le code manuellement.

J'en parle pour mentionner qu'il est possible de jeter un œil au format des vraies clés privées utilisées dans l'industrie. Il s'agit généralement de chaînes codées en Base32 de 16 à 32 caractères qui ressemblent à ceci :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Comme vous pouvez le constater, ceci est assez différent des clés privées entières que nous avons utilisées, et l'implémentation actuelle de notre algorithme ne fonctionnera pas si nous devons passer à ce format. Comment pouvons-nous ajuster notre logique ?

Clé privée sous forme de chaîne

Commençons par une approche simple : notre code ne se compilera pas, à cause de cette ligne :

Current timestamp:  1733691162
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

car pk est désormais de type string. Alors pourquoi ne pas le convertir en entier ? Bien qu'il existe des moyens bien plus élégants et performants de le faire, voici la chose la plus simple que j'ai trouvée :

// gets current timestamp
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds
base := current / 30
fmt.Println("Base: ", base)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ceci est fortement inspiré de l'implémentation Java hashCode() pour le type de données String, ce qui le rend suffisant pour notre scénario.

Voici la logique ajustée :

Current timestamp:  1733691545
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Voici la sortie du terminal :

Current timestamp:  1733691552
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Bien, le code à 6 chiffres est là, bon travail. Attendons d'arriver à la fenêtre horaire suivante et exécutons-la à nouveau :

Current timestamp:  1733691571
Base:  57789719
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Hm...ça marche, mais le code est, en gros, l'incrément de la valeur précédente, ce qui n'est pas bon, car de cette façon les OTP sont prévisibles, et avoir sa valeur et savoir à quelle heure il appartient, c'est très simple pour commencer à générer les mêmes valeurs sans avoir besoin de connaître la clé privée. Voici le pseudocode simple de ce hack :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds:
base := current / 30
fmt.Println("Base: ", base)

// makes sure it has only 6 digits:
code := base % 1_000_000

// adds leading zeros if necessary:
formattedCode := fmt.Sprintf("%06d", code)
fmt.Println("Code: ", formattedCode)
Copier après la connexion
Copier après la connexion
Copier après la connexion

où keepWithinSixDigits s'assurera qu'après 999 999, la valeur suivante est 000 000 et ainsi de suite pour maintenir la valeur dans la limite des possibilités à 6 chiffres.

Comme vous pouvez le constater, il s’agit d’une grave faille de sécurité. Pourquoi cela arrive-t-il ? Si l'on regarde la logique de calcul de base, nous verrons qu'elle repose sur 2 facteurs :

  • horodatage actuel divisé par 30
  • hachage de la clé privée

Le hachage produit la même valeur pour la même clé, sa valeur est donc constante. Quant au /30 actuel, il a la même valeur pendant 30 secondes, mais une fois la fenêtre passée, la valeur suivante sera l'incrément de la précédente. Ensuite, la base % 1_000_000 se comporte comme nous le voyons. Notre implémentation précédente (avec des clés privées sous forme d'entiers) présentait la même vulnérabilité, mais nous ne l'avons pas remarqué - le manque de tests est à blâmer.

Nous devons transformer le courant / 30 en quelque chose pour rendre le changement de sa valeur plus visible.

Valeurs OTP distribuées

Il existe plusieurs façons d'y parvenir, et quelques astuces mathématiques intéressantes existent, mais à des fins éducatives, donnons la priorité à la lisibilité de la solution que nous utiliserons : extrayons le courant / 30 dans une base de variables distincte et incluons dans la logique de calcul de hachage :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

De cette façon, même si la base changera de 1 toutes les 30 secondes, après avoir été utilisée dans la logique de la fonction hash(), le poids du diff augmentera en raison de la série de multiplications effectuées.

Voici l'exemple de code mis à jour :

Current timestamp:  1733691162
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Exécutons :

// gets current timestamp
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds
base := current / 30
fmt.Println("Base: ", base)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Boum ! Comment se fait-il que nous ayons obtenu la valeur négative ici ? Eh bien, il semble que nous ayons manqué de plages int64, nous avons donc plafonné les valeurs à moins et avons recommencé - mes collègues Java le connaissent grâce au comportement hashCode(). La solution est simple : prenons la valeur absolue du résultat, donc le signe moins est ignoré :

Current timestamp:  1733691545
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Voici un exemple de code complet avec le correctif :

Current timestamp:  1733691552
Base:  57789718
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Exécutons :

Current timestamp:  1733691571
Base:  57789719
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Exécutons-le à nouveau pour nous assurer que les valeurs OTP sont distribuées maintenant :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds:
base := current / 30
fmt.Println("Base: ", base)

// makes sure it has only 6 digits:
code := base % 1_000_000

// adds leading zeros if necessary:
formattedCode := fmt.Sprintf("%06d", code)
fmt.Println("Code: ", formattedCode)
Copier après la connexion
Copier après la connexion
Copier après la connexion

Bien, enfin une solution décente !

En fait, c'est à ce moment-là que j'ai arrêté mon processus de mise en œuvre manuelle, car j'ai eu ma part de plaisir et j'ai appris quelque chose de nouveau. Cependant, ce n’est ni la meilleure solution ni celle avec laquelle je partirais vivre. Entre autres choses, il présente un gros défaut : comme vous pouvez le constater, notre logique traite toujours de grands nombres en raison de la logique de hachage et des valeurs d'horodatage, ce qui signifie qu'il est très peu probable que nous puissions générer des résultats commençant par 1 ou plus de zéros : par exemple, 012345 , 001234, etc., même s'ils sont totalement valides. Pour cette raison, il nous manque 100 000 valeurs possibles, ce qui représente 10 % du nombre possible de résultats de l'algorithme – les chances de collisions sont ainsi plus élevées. Pas cool !

Où aller à partir d'ici

Je n'entrerai pas dans les détails de l'implémentation utilisée dans les applications réelles, mais pour ceux qui sont curieux, je partagerai deux RFC qui valent la peine d'être consultées :

  • HOTP : un algorithme de mot de passe à usage unique basé sur HMAC
  • TOTP : algorithme de mot de passe à usage unique basé sur le temps

Et voici l'implémentation du pseudocode qui fonctionnera comme prévu en fonction des RFC ci-dessus :

Current timestamp:  1733692423
Base:  57789747
Code:  789747
Copier après la connexion

Comme vous pouvez le voir, nous en sommes très proches, mais l'algorithme d'origine utilise un hachage plus avancé (HMAC-SHA1 dans cet exemple) et effectue quelques opérations au niveau du bit pour normaliser la sortie.

Considérations de sécurité : réutiliser plutôt que construire soi-même

Cependant, il y a encore une chose que j'aimerais aborder avant de mettre fin à cette journée : la sécurité. Je vous encourage fortement à ne pas implémenter la logique de génération d'OTP par vous-même, car de nombreuses bibliothèques le font pour nous. La marge d’erreur est énorme, et la vulnérabilité qui sera découverte et exploitée par les mauvais acteurs est proche.

Même si vous maîtrisez la logique de génération et la couvrez de tests, il y a d'autres choses qui peuvent mal tourner. Par exemple, combien pensez-vous qu’il faudra pour forcer brutalement le code à 6 chiffres ? Expérimentons :

// gets current timestamp:
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Exécutons ce code :

Current timestamp:  1733691162
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et encore une fois :

// gets current timestamp
current := time.Now().Unix()
fmt.Println("Current timestamp: ", current)

// gets a number that is stable within 30 seconds
base := current / 30
fmt.Println("Base: ", base)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Comme vous pouvez le voir, il faut environ 70 ms pour deviner le code via une simple boucle for de forçage brut. C'est 400 fois plus rapide que la durée de vie de l'OTP ! Le serveur de l'application/du site Web utilisant le mécanisme OTP doit empêcher cela, par exemple en n'acceptant pas de nouveaux codes pendant les 5 ou 10 secondes suivantes après 3 tentatives infructueuses. De cette façon, l'attaquant n'obtient que 18 ou 9 tentatives dans la fenêtre de 30 secondes, ce qui n'est pas suffisant pour le pool de 1 million de valeurs possibles.

Et il y a d’autres choses comme celle-ci qui sont faciles à négliger. Alors, permettez-moi de le répéter : ne construisez pas cela à partir de zéro, mais comptez sur les solutions existantes.

Quoi qu'il en soit, j'espère que vous avez appris quelque chose de nouveau aujourd'hui, et que la logique d'OTP ne sera plus un mystère pour vous à partir de maintenant. De plus, si à un moment donné de votre vie, vous devez faire en sorte que votre appareil hors ligne génère des valeurs à l'aide d'un algorithme reproductible, vous avez une bonne idée par où commencer.

Merci pour le temps que vous avez passé à lire cet article et amusez-vous ! =)

P.S. Recevez un e-mail une fois que je publie un nouvel article - abonnez-vous ici

P.P.S. Comme le reste des cool kids, j'ai créé un compte Bluesky récemment, alors, s'il vous plaît, aidez-moi à rendre mon flux plus amusant =)

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal