J'ai toujours voulu qu'un pirate soit incapable de déchiffrer le mot de passe de quelqu'un d'autre sur le site.
Par exemple, si un utilisateur se vante auprès d'un pirate informatique que son mot de passe ne se compose que de chiffres, il perdra bientôt son compte.
Mais que se passe-t-il si l'utilisateur communique son mot de passe à sa femme par téléphone et que le pirate l'entend?
Quoi?! Le hacker connaît-il le mot de passe? Tout cela est un fiasco. Pouvez-vous aider un tel utilisateur à rendre plus difficile le piratage de son compte? Cette question m'a toujours préoccupé et je pense avoir trouvé un moyen de le faire. Ou redécouvert, comme c'est souvent le cas. Après tout, tout a longtemps été inventé avant nous.
Introduction
- L'utilisateur souhaite avoir un mot de passe sur le site "12345".
- Un hacker peut facilement deviner ce mot de passe.
- Mais l'utilisateur doit se connecter, et le pirate ne le fait pas. Même si le pirate connaît le login et le mot de passe.
- et pas de SMS avec codes secrets et intermédiaires sous forme de services supplémentaires. Seul l'utilisateur et votre site avec une page de connexion.
- et il sera également relativement sûr de dire à votre femme dans un trolleybus: «Galya, j'ai changé le mot de passe en 123456 sur le site du site pour notre login alice - ils disent qu'il est plus populaire que notre 12345». Et n'ayez pas peur que votre compte soit piraté dans une seconde.
Comment fonctionne la méthode? Tous les détails sont sous la coupe.
Ce qui est requis?
- le concept n'explique que la méthode d'authentification
- l'implémentation nécessite de stocker uniquement " username ", " password ", " salt1 " et " salt2 ". Oui, deux sels.
- se passer des tables de journalisation et des compteurs dans redis
- nous ne garderons pas de tables avec des adresses IP
- nous n'utiliserons pas de SMS
- nous ne bloquerons pas les tentatives de connexion. Comme vous le savez d'après ma dernière tentative infructueuse, il est inutile de bloquer l'entrée - même si un pirate informatique se heurte à une limite de temps, il commencera simplement à contourner les mots de passe de plusieurs utilisateurs à la fois. De plus, l'utilisateur lui-même souffrira des restrictions. Ne l'appelez pas en support pour vous connecter à votre site avec des images sympas?
- l'utilisateur peut modifier le mot de passe à tout moment et le rendre invalide sur d'autres appareils. C'est une règle courante, mais je pense que cela vaut la peine d'être mentionné.
- vous pouvez rendre le processus de deviner un mot de passe à l'aide d'un dictionnaire plus difficile pour un hacker (facultatif, sera mentionné ci-dessous).
Essence de méthode
Permettez à l'utilisateur d'avoir le mot de passe "12345", et le craquage de ce mot de passe devrait être rendu plus difficile. Par exemple, comment deviner un mot de passe qui ressemble à un hachage.
Comment?
Imaginez si le navigateur avait toujours un sel unique avec lequel saler le mot de passe. Du sel pour chaque utilisateur. Pourquoi est-ce nécessaire? Pour crypter. Par exemple, si vous cryptez la chaîne «12345» avec le sel «sel» dans argon2id, vous obtenez «$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg». Changez le sel et le hachage sera différent. Un algorithme cryptera les mêmes mots de passe différemment en utilisant un sel différent pour chacun. Bien.
Mais où se procurer ce sel au départ? Oui, la voici assise devant le moniteur. Laissez-le presser deux ou trois personnages supplémentaires et connectez-vous enfin humainement. Y a-t-il un chat qui court? Eh bien, allons avoir un chat. Qu'est-ce que le chat? Ceci est notre mot secret. Nous l'enverrons au serveur lors de l'inscription, et il générera du sel pour ce mot. Et puis il nous enverra ce sel. Voilà - le navigateur a du sel. Maintenant, le mot de passe. Et nous chiffrons également le mot de passe et le salons avec le sel envoyé par le serveur.
Désormais, nous ne portons pas de casque "12345". Nous envoyons un hachage, et comme chaque utilisateur a son propre sel, le hachage est différent.
Il semble que la force brute va tomber malade maintenant: non seulement vous devrez faire des calculs supplémentaires et itérer sur de longues chaînes de hachages argon au lieu de simples nombres, de sorte que chaque utilisateur aura également son propre hachage - maintenant il est inutile d'essayer la même chaîne comme mot de passe pour le vérifier pour tout le monde utilisateurs. Disons que trois utilisateurs ont choisi le même mot de passe: 12345. Mais leur hachage sera différent. Parce que tout le monde a un sel différent.
- Le navigateur doit calculer le hachage du mot de passe en utilisant le sel que le serveur lui a envoyé plus tôt. Il doit envoyer un hachage, pas le mot de passe lui-même.
- Le serveur envoie du sel en utilisant un mot secret qui n'est connu que de l'utilisateur. Cela peut être simple. Par exemple - "chat".
- Chaque utilisateur doit avoir son propre sel.
- Deux utilisateurs qui ont choisi le même mot secret doivent avoir un sel différent.
- Le serveur n'a pas à indiquer si le mot secret correct a été utilisé et si le sel est correct pour cet utilisateur - sinon, ce sera par force brute deux mots de passe simples au lieu d'un.
- Si l'utilisateur change le mot secret, le sel change également.
Autrement dit, pour protéger son mot de passe simple, l'utilisateur doit trouver un autre mot très simple. Il entre ce mot partout où il veut être authentifié, puis seul le mot de passe devra être saisi. Jusqu'à ce qu'il efface les cookies.
- est allé sur le site
- login entré et mot secret
- mot de passe entré
- prêt
Le mot de passe et le mot secret peuvent être très simples. Un ou deux personnages. Par exemple, le mot de passe est 12345 et le mot secret 42. Et si quelqu'un d'autre trouve le mot secret 42, alors ce ne sera pas effrayant.
Comment ça fonctionne. Concept étape par étape
Nous avons les éléments suivants:
- serveur Web
- base de données et table des utilisateurs:
- s'identifier
- password_hash
- salt_unique_for_each_user
- salt_for_password
- navigateur de l'utilisateur
- navigateur pirate
- pages de connexion et d'inscription sur le site
- script qui intercepte l'événement de soumission pour le formulaire de connexion
Ensuite, nous avons besoin de deux algorithmes différents qui peuvent être implémentés même sur un système de cryptage, simplement avec des paramètres différents:
- ALG1 est un algorithme de chiffrement asymétrique qui génère un hachage à partir d'une chaîne et d'un sel. ALG1 (chaîne, sel) = hash1. Cet algorithme n'est utilisé que sur le serveur.
- ALG2 est un algorithme de chiffrement asymétrique qui génère un hachage à partir d'une chaîne et d'un sel. ALG2 (chaîne, sel) = hash2. Cet algorithme est utilisé publiquement et il devrait être possible de l'implémenter sur le client (dans notre exemple, en javascript).
De plus, nous avons besoin de deux algorithmes plus simples:
- ALG_SALT est un algorithme qui calcule un sel aléatoire sous forme de chaîne de caractères. ALG_SALT () = sel. Cet algorithme n'est utilisé que sur le serveur.
- ALG_PASS est un algorithme qui génère un mot de passe simple aléatoire. ALG_PASS () = passer. Cet algorithme n'est utilisé que sur le serveur.
Événements étape par étape
- L'utilisateur accède à la page d'inscription, car il n'a pas encore de login.
- Le serveur affiche un formulaire avec deux champs: login + simple mot secret.
- L'utilisateur sélectionne la connexion - Alice
- L'utilisateur choisit le mot secret - chat
- L'utilisateur clique sur le bouton Soumettre .
Le serveur vérifie et s'assure que l'utilisateur alice n'est pas présent dans la base de données.
Le serveur calcule les valeurs suivantes:
$salt_unique_for_each_user = ALG_SALT(); // "saltsalt"
$salt_for_password = ALG1("cat", $salt_unique_for_each_user); // "$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
$user_simple_password = ALG_PASS(); // "12345"
$user_simple_password_hashed = ALG2($user_simple_password , $salt_for_password); // "$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ"
Le serveur crée un enregistrement dans la table des utilisateurs et enregistre les données:
INSERT INTO `users`
(
login,
password_hashed,
salt_unique_for_each_user,
salt_for_password
)
VALUES
(
"alice",
"$argon2id$v=19$m=16,t=2,p=1$JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvOUFoUytiSHdia2c$b+6ROJVsZ62UXA7hEAg0AQ",
"saltsalt",
"$argon2id$v=19$m=16,t=2,p=1$c2FsdHNhbHQ$jX94laSi6vo9AhS+bHwbkg"
).
Le serveur montre à l'utilisateur une page de réussite de l'enregistrement avec le message: «L'utilisateur alice a été créé avec succès. Utilisez le mot de passe temporaire 12345 pour vous connecter. "
L'utilisateur crie joyeusement: "Hourra, je me suis inscrit sur le site sous le pseudo alice et ils m'ont donné le mot de passe 12345. Quel mot de passe drôle et simple!". Mais l'appartement de l'utilisateur est très insonorisé et son voisin hacker a tout entendu.
- Le pirate pousse l'adresse du site Web dans son navigateur.
- Le navigateur du pirate envoie des cookies vides.
- Le serveur vérifie la demande du hacker pour voir s'il y a un cookie "salt". Ne la trouve pas.
- Avant que le pirate n'envoie le nom d'utilisateur et le mot de passe volés, le navigateur a besoin de connaître le sel pour crypter le mot de passe avec lui.
- Le navigateur du hacker ne stocke pas encore le sel dans le cookie "salt".
- Le serveur envoie un formulaire de connexion avec deux champs: login + mot secret pour permettre à l'utilisateur d'obtenir le sel.
Le hacker est confus. Laissons-le pour l'instant.
- L'utilisateur est renvoyé à la page de connexion.
- Le navigateur de l'utilisateur envoie des cookies vides.
- Le serveur vérifie la demande de l'utilisateur pour voir s'il existe un cookie "salt". Ne la trouve pas.
- Avant que l'utilisateur envoie un nom d'utilisateur et un mot de passe, le navigateur doit connaître le sel afin de crypter le mot de passe avec celui-ci.
- Le navigateur de l'utilisateur ne stocke pas encore le sel dans le cookie "sel".
- Le serveur envoie un formulaire de connexion avec deux champs: login + mot secret pour permettre à l'utilisateur d'obtenir le sel.
- L'utilisateur entre login - alice , secret - cat et clique sur le bouton « Soumettre ».
Le serveur reçoit la demande et voit qu'à la place du mot de passe, un mot secret a été envoyé.
- — alice `salt_unique_for_each_user` -> $db_salt_unique_for_each_user `salt_for_password -> $db_salt_for_password`.
- , . : $salt_for_password = ALG1(«cat», $db_salt_unique_for_each_user).
- $salt_for_password . . 12345, , . — ` salt = $db_salt_for_password`. : ` login = «alice»`.
Explication : Le serveur ne notifie en aucune manière quel sel a été envoyé - correct ou non. Le résultat de son utilisation sera clair lorsqu'ils essaieront de se connecter avec le nom d'utilisateur et le mot de passe corrects.
- L'utilisateur reçoit une réponse du serveur. Sa page se recharge ou change immédiatement de manière dynamique.
- Le navigateur de l'utilisateur envoie des cookies: login = alice , salt = "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg" .
- Le serveur vérifie la demande de l'utilisateur pour voir s'il existe un cookie "salt". La trouve.
- Le navigateur a déjà du sel pour crypter le mot de passe.
- Le serveur envoie un formulaire de connexion avec deux champs: login (déjà alice ) + mot de passe.
- L'utilisateur entre son simple mot de passe 12345 et clique sur le bouton « Soumettre ».
- Le navigateur intercepte l'événement onSubmit .
- Calcule $ password_hashed = ALG2 ("12345", "$ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ c2FsdHNhbHQ $ jX94laSi6vo9AhS + bHwbkg").
- Envoie les données «alice» / $ argon2id $ v = 19 $ m = 16, t = 2, p = 1 $ JGFyZ29uMmlkJHY9MTkkbT0xNix0PTIscD0xJGMyRnNkSE5oYkhRJGpYOTRsYVNpNnZvHOUFo
Le serveur reçoit une demande d'authentification:
- Données de connexion + mot de passe: "alice" / $ password_hashed
- Va dans la base de données, obtient la valeur ` password_hashed` -> $ db_password_hashed.
- Compare $ db_password_hashed === $ password_hashed?
- Les hachages correspondent, l'autorisation a réussi.
Remarque: Dans mon exemple, le serveur compare directement les hachages. Mais vous ne pouvez pas stocker dans la base de données des lignes qui sont déjà des mots de passe. Ils peuvent être volés puis utilisés sous la forme d'un mot de passe de connexion. Par conséquent, vous devez hacher des hachages - aussi étrange que cela puisse paraître. Cela signifie que vous avez besoin d'un troisième sel. Mais il ne doit pas être stocké dans la base de données, mais dans la variable d'environnement. Cependant, ce sont déjà des détails de mise en œuvre que j'ai omis pour des raisons de simplicité.
En attendant, notre hacker décide de tester cet étrange formulaire de connexion:
- Le hacker entre login - Alice , Secret - Dog et clique sur le bouton « Soumettre ».
- Le serveur reçoit la demande d'un hacker et voit qu'un mot secret a été envoyé à la place d'un mot de passe.
- — alice `salt_unique_for_each_user` -> $db_salt_unique_for_each_user `salt_for_password` -> $salt_for_password.
- , , : $result_fake_salt = ALG1(«dog», $db_salt_unique_for_each_user). , .
Le serveur renvoie la valeur de sel calculée au navigateur de l'utilisateur. Les en-têtes indiquent - `set cookie salt = $ result_fake_salt`. Le login est également enregistré: `set the cookie login =" alice "`.
Explication : Pour aider le pirate dans son travail acharné, le serveur lui envoie du sel. Mais il est impossible de déterminer de l'extérieur si le mot secret était correct ou non.
- Le pirate reçoit la réponse du serveur. Sa page se recharge ou change immédiatement de manière dynamique.
- Le navigateur du pirate envoie des cookies: login = alice , salt = $ result_fake_salt .
- Le serveur vérifie la demande de l'utilisateur pour voir s'il existe un cookie "salt". La trouve.
- Le navigateur du pirate a déjà du sel pour crypter le mot de passe.
- : ( alice) + .
- 12345 "".
- onSubmit.
- $password_hashed = ALG2(«12345», $result_fake_salt).
- «alice»/$password_hashed.
Le serveur reçoit une demande d'authentification - "alice" / $ password_hashed.
Va dans la base de données, obtient la valeur `password_hashed` -> $ db_password_hashed.
Compare: $ password_hashed === $ db_password_hashed? Nan.
Les hachages de ces mots de passe initialement identiques ne correspondent pas. Parce qu'ils étaient salés de différentes manières.
Le hacker n'abandonne pas et va enregistrer un autre utilisateur sur le site.
Tout à fait par accident, il entre le même mot secret que l'utilisateur derrière le mur - chat .
Le pirate obtient un sel de mot de passe valide pour le nouveau compte et essaie de le remplacer dans le script de hachage.
Heureusement, la génération de sel de mot de passe a utilisé un second salt (`salt_unique_for_each_user`), qui est généré d'une nouvelle manière pour chaque utilisateur. Ainsi, différents utilisateurs, même avec les mêmes mots de passe et, surtout, des mots secrets, auront des sels différents. Et le sel de l'utilisateur avec le même mot secret ne correspondra pas au sel d'un autre. Et la correspondance des mots de passe ne sera pas non plus un problème.
Maintenant, concernant la complication des mots de passe de force brute dans un dictionnaire. Si nous modifions ALG2, qui est commun à la fois au serveur et au client, et le rendons laborieux, cela compliquera sérieusement l'attaque pour le pirate. Permettez-moi de vous rappeler que ALG2 est le processus d'obtention d'un hachage de mot de passe qui est envoyé au serveur. Sur le serveur, ce hachage a déjà été calculé et stocké dans la base de données:
- le serveur n'effectuera l'opération ALG2 qu'une seule fois lors de l'écriture d'un mot de passe dans la base de données ou lors du changement du mot de passe par un nouveau
- le client n'effectuera l'opération ALG2 que pendant l'authentification (à ne pas confondre avec l'autorisation). Disons que le client a fait une erreur plusieurs fois lors de la saisie du mot de passe - ce n'est pas grave.
- Le hacker le fera tout le temps pour chaque mot de passe, ce pour quoi il peut être félicité. Il est particulièrement cynique qu'un effort titanesque soit consacré à des mots de passe comme 123/1234/12345.
Sur les machines faibles, l'opération peut prendre beaucoup plus de temps que sur les machines rapides. Cela peut être un problème. Vous n'avez donc pas à compliquer l'algorithme.
Je terminerai la description du concept avec un baril de goudron:
- Si un utilisateur entre accidentellement un mot secret de manière incorrecte, il se trouvera dans une situation où il ne pourra pas entrer en utilisant son mot de passe. Vous devrez réinitialiser le mot secret (dans notre cas, supprimer les cookies) et renvoyer la demande. Cela peut être implémenté de manière transparente en appuyant sur un bouton, mais avant cela, l'utilisateur doit deviner. Vous pouvez forcer une réinitialisation sur 5 tentatives de connexion incorrectes.
- Deux utilisateurs sur le même ordinateur devront constamment se vider mutuellement.
- Deux ordinateurs différents recevront le même sel de mot de passe
- Si le sel est changé sur le serveur via un ordinateur, l'autre ordinateur avec l'ancien sel ne saura pas qu'il doit être changé
- Vous pouvez voler du sel sur votre ordinateur et l'utiliser pour mener une attaque très rapide sur votre compte, sachant que le mot de passe est très simple.
... et une cuillerée de miel:
- . , "cat" , "termorectal" — . , . , . , .
- . `salt_for_password` , , , . .