Tous les ans on voit apparaître un peu partout des listings du genre "Voici le top 200 des pires mots de passe de 2020". Tous les ans je me dis qu'il faudrait qu'on parle un peu plus des bases pour biens stocker des mots de passe pour éviter que ceux-ci se retrouvent dans un de ces listings.
Je vous donne tout de suite la règle la plus importante : bcrypt(salt+pepper+sha512(password)).
Les hashs c'est lent et pas pratique, on stocke en clair ?
C'est souvent le genre de chose qu'on peut entendre. Ou bien "si l'utilisateur perd son mot de passe on peut lui redonner !" (pour moi c'est limite pire comme raisonnement…
En tout cas c'est le choix qui est fait par encore beaucoup de gens. Malheureusement, à la moindre fuite de donnée, tout votre système est compris. En plus d'être potentiellement dommageable pour vos utilisateurs (et je ne pense pas seulement à un vol de compte sur votre site, mais aussi au fait que beaucoup d'utilisateur utilise le même mot de passe-partout), ça peut très vite nuire à l'image de votre produit.
Donc s'il vous plaît ne faites pas ça !
username | password |
---|---|
Bob | password1 |
Alice | password1 |
Paul | 123456 |
Stocker sous forme de hash
Qui dit hash dit md5 ou sha1 ! Ou pas…
Petit rappel quand même : un algorithme de hash a pour but de transformer une chaine de caractère en une autre, un peu à la manière du chiffrement ou de la compression, mais dans le cas d'un hash le but est qu'on ne puisse pas renverser la transformation pour retrouver la chaîne originale. Les deux grandes applications sont : la protection des mots de passe (qu'on traite ici) et le contrôle d'intégrité (par exemple pour vérifier après téléchargement qu'un fichier n'est pas corrompu).
Dans l'idée c'est le premier pas, au sens où c'est un poil mieux que sauvegarder les mots de passes en clair. Mais par contre md5 et sha1, bien que très rapide à calculer, posent des gros problèmes de sécurité : on peut relativement créer une collision (2 phrases en entrée qui donne le même hash), il y a aussi beaucoup de dictionnaire de hash (souvent appelé rainbow table) qui permettre en moins d'une seconde de retrouver le mot de passe d'origine à partir d'un hash.
Dans le même la vitesse de calcul du hash peut sembler un point fort mais c'est un très gros point faible au contraire : un pirate pourrait calculer très très vite en force brute énormément de mot de passe pour déchiffrer les mots de passes que vous avez stockés.
username | password |
---|---|
Bob | 7c6a180b36896a0a8c02787eeafb0e4c |
Alice | 7c6a180b36896a0a8c02787eeafb0e4c |
Paul | e10adc3949ba59abbe56e057f20f883e |
Stocker avec un bon hash c'est mieux !
J'ai creusé le sujet et je reste aujourd'hui sur l'algorithme bcrypt. Il a un faible taux de collision, les calculs sont plutôt lents ce qui rend difficile la création de dictionnaire mais suffisamment rapide pour ne pas poser problème pour un usage courant d'authentification (j'ai trouvé des estimations de calcul sous les 100ms).
Cet algorithme a été créé en 1999, il n'est donc pas récent en soi. Mais il a été mis à jour plusieurs fois, la dernière fois étant en 2014 où une faille dans l'implémentation de référence a été trouvé (plus d'info ici).
Le plus gros défaut à l'usage avec bcrypt c'est qu'il ne gère pas les entrées de plus de 71 caractères et si on lui donne une chaîne plus grande elle sera tronquée à 71. On recommande souvent de le combiner avec sha512 (une des variantes de sha2) qui est un algorithme très rapide et qui donne des hash de 512 bits. De cette façon on peut se permettre de dépasser la limite des 71 caractères de bcrypt de manière raisonnable.
username | password |
---|---|
Bob | $2y$10$AQqD/T3PwqOmJUB2t.ayUODUIx4qlWozt6LYHibI32Z8Q89GedEaC |
Alice | $2y$10$AQqD/T3PwqOmJUB2t.ayUODUIx4qlWozt6LYHibI32Z8Q89GedEaC |
Paul | $2y$10$XqGWGtNl9yqthWmaPZCBlOR5EO954Nc18EkycckefjCGsKZWG6dYq |
Une dose de 🧂 par utilisateur ?
Le problème des algorithmes de hash reste encore et toujours les dictionnaires. De plus si on passe par la bcrypt(sha512(password)) comme beaucoup de gens le recommandent, il y a de forte chance qu'un dictionnaire existe déjà quelque part pour déchiffrer notre base au moins en partie.
De plus on obtient aussi une base qui contiendra des hash identiques pour deux utilisateurs ayant le même mot de passe (ce qui augmente l'intérêt pour un pirate de déchiffrer ce hash).
La solution c'est d'ajouter du sel à notre système. Ce sel c'est simplement un petit mot généré aléatoirement, qu'on va stocker quelque part pour le retrouver. Si avant il arrivait qu'on propose qu'il soit en dur dans la configuration ou dans le code, on préférera le stocker en clair dans la base avec l'identifiant et le mot de passe de l'utilisateur. Le but est de rendre très difficile la création d'un dictionnaire.
On passera donc une formule de hash comme ça : bcrypt(salt+sha512(password)).
Vous noterez que j'ai placé le sel au début dans ma formule. Dans le cas d'un autre algorithme, on le placera indifféremment au début ou la fin, mais pour bcrypt qui va potentiellement tronquer notre entrée, on s'assurera de conserver le sel pour le traitement.
username | salt | password |
---|---|---|
Bob | ogIFne9VDvh | $2y$10$EXGK6O8M8p1KG0BVq/KhTOQKcimXledXXLcA5uZBwRt4UVfe.PzkG |
Alice | VziEWUEh10Z | $2y$10$WeZbMpBpNqddPsGsspO4xeC2EkBl.R9A83Vaj6y7YNR89sKXEmK4q |
Paul | 2nikT3GZj5a | $2y$10$R8Xru999wJ4HPfywYp4B8eUtCUwaE1c9TQSzSBqKM2/.mesdJO9ku |
Et pourquoi pas un peu de poivre ?
À l'étape précédente, comme on stocke le sel avec le mot de passe hashé, on arrive à un problème : il est possible théoriquement simplement avec la base de donnée de calculer un dictionnaire pour renverser les mots de passe.
Une possibilité pour éviter ça c'est d'utiliser un poivre (pepper). Celui-ci sera stocké soit en dur dans le code ou dans un fichier de configuration. De cette manière un vol de la base ne donnera pas toutes les cartes pour déchiffrer les mots de passes.
Comme pour le sel, on va générer le poivre via un générateur pseudo aléatoire pour garantir une certaine difficulté à le deviner pour un attaquant.
username | salt | password |
---|---|---|
Bob | ogIFne9VDvh | $2y$10$kOeScGG15RAXkNgEZSPqNeVYK3N1rt9wuYq/W/OjtbLtxHMR8atYS |
Alice | VziEWUEh10Z | $2y$10$uych.JvvDGfX38draQ.nD.sD2Xd6puS7aZjgVQpIvJ4feFip02MCa |
Paul | 2nikT3GZj5a | $2y$10$CH6hsjgkc8EqW2/eg9NbQ.2NzS1nYNtisYtuCMvUFgTahUDk/iHsy |
Conclusion
Sans dire que je vous ai résumé ici tout ce qu'on peut connaître sur le sujet. J'ai essayé de faire un résumé de beaucoup de bonne pratique, que je connaissais, que j'ai redécouverte en préparant cet article et que j'ai glané en lisant la manière de faire de certaines grandes entreprises tech comme Dropbox (voir les sources).
Essayez de les appliquer quand vous le pouvez ou d'essayer de faire pencher la balance dans la bonne direction au minimum ! 😇
Sources:
- https://auth0.com/blog/hashing-passwords-one-way-road-to-security/
- https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/
- https://www.maketecheasier.com/what-is-password-hashing/
- https://en.wikipedia.org/wiki/Bcrypt
- https://www.universfreebox.com/article/59388/palmares-des-pires-mots-de-passe-de-2020-le-123456-toujours-indetronable
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- https://dropbox.tech/security/how-dropbox-securely-stores-your-passwords
- https://www.dcode.fr/hash-sha512
- https://www.bcrypt.fr/
Crédit photo : https://pixabay.com/illustrations/login-password-log-sign-on-turn-on-1203603/