Stockage de mots de passe #1 : le hachage
12 Jan 2020
J'ai déjà parlé de l'art de bien choisir son mot de passe, d'utiliser éventuellement un gestionnaire de mots de passe et d'activer la double authentification quand c'était possible.
Je voudrais maintenant parler de la façon dont est (ou devrait être) géré le stockage des mots de passe, car on attend parler tous les 4 matins d'un nouveau site qui s'est fait hacker sa base de données avec des mots de passes dedans.
Tout d'abord un peu de théorie.
Mot de passe en clair
A l'origine, le fonctionnement était le suivant :
- un utilisateur créé son compte sur un service (site web ou autre) en renseignant un identifiant (ou login) et un mot de passe
- le service stocke l'identifiant et le mot de passe dans une base de données
- lorsque l'utilisateur s'authentifie sur le service, ce dernier récupère l'identifiant et le mot de passe indiqués par l'utilisateur et vérifie que le mot de passe correspond bien à celui stocké en base de données pour cet identifiant
Jusque là, rien d'extraordinaire, c'est super simple.
Problème : si quelqu'un accède à la base de données alors il peut voir l'identifiant et le mot de passe de n'importe qui et se connecter à sa place.
Ce n'est pas top.
Mot de passe haché
Afin de remédier à ce problème, les mots de passe ne sont pas stockés en clair (ou plaintext en anglais) mais sous forme chiffrée que l'on appelle un hash ou mot de passe haché.
Par exemple le mot de passe password1234 devient 4420d1918bbcf7686defdf9570bb5087d20076de5f77b7cb4c3b40bf46ec428b.
L'intérêt est de stocker le mot de passe avec une valeur différente de celle d'origine, afin que même si on a accès à la base de données on n'ait pas connaissance de la vraie valeur du mot de passe.
Pour obtenir ce mot de passe haché, il faut utiliser une fonction de hachage, qui va transformer le mot de passe en une nouvelle valeur.
Le fonctionnement devient :
- inchangé : un utilisateur créé son compte sur un service (site web…) en renseignant un identifiant (ou login) et un mot de passe
- le service stocke l'identifiant et transforme le mot de passe en clair par un mot de passe haché via la fonction de hachage, et le stocke sous sa forme hachée dans une base de données
- lorsque l'utilisateur s'authentifie sur le service, ce dernier récupère l'identifiant et le mot de passe indiqués par l'utilisateur, transforme le mot de passe en clair en un mot de passe haché via la fonction de hachage et vérifie que le mot de passe haché correspond bien à celui stocké en base de données pour cet identifiant
Nouveau problème : si quelqu'un peut facilement trouver le mot de passe en clair à partir du mot de passe haché alors la mise en place de ce système n'a pas d'intérêt.
Particularités de la fonction de hachage
Une fonction de hachage est un algorithme mathématique qui prend en entrée une chaine de caractères de n'importe quelle taille et qui la transforme en une chaine de caractères de taille fixe. Egalement, pour une valeur d'origine, il faut toujours que la valeur hachée soit la même, en langage mathématique on dit que la fonction est déterministe.
La propriété principale d'une fonction de hachage est de rendre le plus difficile possible l'action de calculer la valeur d'origine à partir de la valeur hachée. Autrement dit, ça doit être une fonction à sens unique : valeur d'origine -> valeur hachée. En cryptographie ça s'intitule résister à la pré-image (pouvoir retrouver la valeur d'origine à partir de la valeur hachée).
En fait, à partir d'une valeur hachée, il ne doit pas être possible de faire le chemin inverse autrement qu'en testant toutes les valeurs d'origines possibles en espérant retomber sur la valeur hachée que l'on recherche. On appelle cette méthode une attaque par force brute, même si c'est plus connu sous son nom anglais : bruteforce attack.
Les propriétés importantes qui en dérivent :
- qu'il soit facile de calculer la valeur hachée. Mais pas trop rapide non plus, car comme on vient de le voir, il faut éviter que quelqu'un puisse tester toutes les valeurs possibles trop rapidement
- que deux valeurs d'origine très proches donnent deux valeurs hachées qui n'ont rien à voir
Un exemple inventé très très simplifié serait une fonction qui ne conserve que les caractères impairs d'un mot, sur 5 caractères fixes en ajoutant des "e" à la fin si le mot en entrée a moins de 9 caractères.
Ce qui donnerait :
- bienvenue -> bevne
- toto -> tteee
- c'estcomplexetoutça -> cetop
- cestcomplexetoutça -> cscml
On voit bien qu'à partir de la valeur hachée, il est impossible de revenir à la valeur de départ, et que même si les deux derniers mots de passe en clair sont très proches leurs valeurs hachées sont différentes.
Là on se dit qu'on tient quelque chose de sympa, mais on a malheureusement encore pas mal de soucis à régler.
Différents contournements possibles
Attaque par collision
Pour reprendre ma fonction bidon du dessus, supposons que deux utilisateurs aient utilisé comme mot de passe : toto et tata. Le mot de passe haché est dans les deux cas tteee, c'est ce que l'on appelle une collision : deux valeurs d'origine distinctes renvoie la même valeur hachée.
C'est statistiquement impossible d'éviter cette attaque : la fonction accepte en entrée une infinité de valeurs et renvoie en sortie une chaine de caractères de taille fixe et finie. On va forcément tomber sur des valeurs d'origines distinctes qui donnent la même valeur hachée.
Il faut que le risque de collision soit égal au risque de collision si on teste aléatoirement toutes les valeurs possibles, sous-entendu : il ne faut pas qu'il existe de mécanisme qui permette de deviner des valeurs de collision.
Attaque par rainbow tables
Les pirates vont se dire que 75% des gens vont utiliser des mots de passe plutôt simples. Donc au lieu d'essayer toutes les combinaisons possibles, ils vont utiliser des dictionnaires de mots de passe usuels.
Ce sont des sortes de listes de mots immenses qui vont prendre tous les mots du dictionnaire, de wikipedia, d'anciennes bases de données de mots de passe piratées,… ce qui restreint considérablement les possibilités.
Ils vont ensuite passer ces dictionnaires dans les fonctions de hachage les plus utilisées et ils vont stocker toutes les valeurs hachées dans des tableaux, que l'on appelle rainbow tables (ou tables arc-en-ciel en français).
Par exemple on trouvera dedans pour le mot de passe "password1234", la valeur hachée suivant la fonction de hachage H1 : "val1", la valeur hachée suivant la fonction de hachage H2 : "val2"…
Ensuite quand ils voudront savoir à quel mot de passe correspond une valeur de hachage trouvée dans une base de données piratée, ils vont d'abord regarder dans leurs super rainbow tables s'il y a une correspondance, et s'éviter ainsi le fardeau de tester toutes les combinaisons possibles et également de recalculer les valeurs hachées des mots de passe usuels.
Mots de passe hachés identiques = mots de passe en clair identiques
Pas une attaque mais un risque important : tous les utilisateurs ayant le même mot de passe en clair vont obtenir le même mot de passe haché. Ce qui permet d'une part à un pirate de prendre le contrôle de tous les utilisateurs d'un coup s'il parvient à retrouver le mot de passe. Et ça lui donne une indication importante : si plusieurs personnes ont le même mot de passe alors il est nécessairement faible ou facile à deviner, et il y a un risque important qu'il soit découvert via une attaque par rainbow tables.
Spoiler alert : tous ces problèmes seront résolus par le mécanisme de salage, qui sera vu dans un prochain article, celui-ci étant déjà assez dense.
Pour aller plus loin, ces liens sont très clairs et m'ont servi dans la rédaction de cet article :
https://zestedesavoir.com/tutoriels/1895/les-fonctions-de-hachage-cryptographiques/
https://auth0.com/blog/hashing-passwords-one-way-road-to-security/
https://www.ionos.fr/digitalguide/serveur/securite/rainbow-tables/