
Dans un précédent article, nous avons vu pourquoi il était important de stocker les mots de passe en base de données avec des fonctions de hachage robustes telles que Bcrypt et Argon2. Cela permet notamment de rendre totalement ineffectif des attaques brute force ou des attaques par dictionnaire.
Cependant, une problématique est régulièrement relevée sur des applications déjà existantes : comment utiliser les dernières recommandations sur le stockage des mots de passe sur une base de données déjà existante ?
Avant d’entrer dans le vif du sujet, quelques précisions sur les recommandations de l’OWASP sur le stockage des mots de passe :
Dans la suite de l’article, nous allons montrer comment mettre à jour les mots de passe avec la fonction de hachage Argon2id.
Pour ce faire, nous partirons de 2 cas concrets :
Cependant nous n’allons pas implémenter un système de poivre. Dans le cas où vous souhaiteriez rajouter un poivre, il suffit de hasher l’ensemble poivre + mot de passe. Pour rappel, le poivre est identique pour tous les utilisateurs.
Par ailleurs, nous fournirons du code PHP comme exemple, mais les algorithmes peuvent se décliner sur d’autres langages.
En préambule, rappelons que le stockage en clair des mots de passe est interdit dans certains pays comme la France. Il est donc impératif de changer la gestion des mots de passe.
Et si vous partez de zéro (cas que nous ne verrons pas dans cet article), vous pouvez consulter le contenu suivant qui détaille : Comment stocker les mots de passe de manière sécurisée dans une base de données ?
Revenons maintenant à notre sujet.
Dans le cas ici présenté, vu que les mots de passe sont facilement identifiables (car stockés en clair), il suffit de mettre à jour les mots de passe avec l’algorithme suivant :

Ce qui aura pour effet de transformer la table utilisateurs de :
| ID | Name | Password |
|---|---|---|
| 1 | admin | azerty |
| 2 | toto | matrix |
| 3 | billy | yep59f$4txwrr |
| 4 | tata | matrix |
| 5 | titi | freepass |
| 6 | attaquant | tesPwndPassword |
…en :
| ID | Name | Password |
|---|---|---|
| 1 | admin | $argon2id$v=19$m=65536,t=4,p=1$UjYzcS42QmhmLjFsa3lrYQ$7t8mfl Th2JhcVqSTdQ0GwtLtMh6plWECubPEH8NjEUM |
| 2 | toto | $argon2id$v=19$m=65536,t=4,p=1$SURlcDNVUDZFWEVlQy5UYg$mv m0Iohc9nd/KOLP5kAw6/WB+PAK0Nt6QGPTsdQa8aw |
| 3 | billy | $argon2id$v=19$m=65536,t=4,p=1$WTlMMDJDeUVMeDNONXRDaQ$u ccT7LUNmJcj8+ZLqtT+U7m+IJePgCuvv8BxCzRYKG4 |
| 4 | tata | $argon2id$v=19$m=65536,t=4,p=1$c00udFlHMm1LMXhwcDBjVg$NR0I QiLqC0dMSgYHbtEhcAGdrFqhdI4YoOTjtMW1zZA |
| 5 | titi | $argon2id$v=19$m=65536,t=4,p=1$U1Y2dm44cXlMWHp0SkFhVg$lYj9k Lfx6zUNOTDUXHDhpEFyBdSJXDv9qjxA0+4oEYw |
| 6 | attaquant | $argon2id$v=19$m=65536,t=4,p=1$NmFqZVhOaTRsN2t4L3lWRw$UJbZ AJcm7ujhLv/Y/DcyMDqD42ME95l+Cd5Kh2XJ2G0 |
Notons au passage que l’application n’impose pas une bonne politique de mot de passe, ce qui est essentiel pour éviter des risques de compromission. Il est donc fortement recommandé d’imposer l’utilisation de mots de passe forts à la création.
Pour plus d’informations, nous vous renvoyons vers notre article : Comment sécuriser les systèmes d’authentification, de gestion de sessions et de contrôle d’accès de vos applications web ?
Grâce à la fonction password_get_info, uniquement les mots de passe qui ne sont pas de l’Argon2 seront transformés. Cela permet d’exécuter le script plusieurs fois sans risquer de hasher plusieurs fois les mots de passe.
Par ailleurs, il est important de faire une sauvegarde de la base de données pour ne pas risquer de corrompre les utilisateurs en cas de soucis de migration.
NB : si un algorithme de chiffrement est utilisé, il faut directement hasher les mots de passe en clair, et non la donnée chiffrée.
Pour confirmer la présence ou non d’un utilisateur, il suffit de comparer avec la fonction password_verify (ou équivalent dans d’autres langages) le mot de passe et le hash.

Même si l’opération est ici invisible pour l’utilisateur, il est tout de même fortement recommandé d’inciter les utilisateurs à changer leur mot de passe après cette modification.
Le fonctionnement sera le suivant :
On rajoute une colonne sur la table utilisateur, par exemple isUpdatePassword qui vaut false par défaut :
sqlite> ALTER TABLE USERS ADD isUpdatePassword bool default false;
sqlite> select * from USERS;
id|name|password|isUpdatePassword
1|admin|$argon2id$v=19$m=65536,t=4,p=1$UjYzcS42QmhmLjFsa3lrYQ$7t8mflTh2JhcVqSTdQ0GwtLtMh6plWECubPEH8NjEUM|0
2|toto|$argon2id$v=19$m=65536,t=4,p=1$SURlcDNVUDZFWEVlQy5UYg$mvm0Iohc9nd/KOLP5kAw6/WB+PAK0Nt6QGPTsdQa8aw|0
3|billy|$argon2id$v=19$m=65536,t=4,p=1$WTlMMDJDeUVMeDNONXRDaQ$uccT7LUNmJcj8+ZLqtT+U7m+IJePgCuvv8BxCzRYKG4|0
4|tata|$argon2id$v=19$m=65536,t=4,p=1$c00udFlHMm1LMXhwcDBjVg$NR0IQiLqC0dMSgYHbtEhcAGdrFqhdI4YoOTjtMW1zZA|0
5|titi|$argon2id$v=19$m=65536,t=4,p=1$U1Y2dm44cXlMWHp0SkFhVg$lYj9kLfx6zUNOTDUXHDhpEFyBdSJXDv9qjxA0+4oEYw|0
6|attaquant|$argon2id$v=19$m=65536,t=4,p=1$NmFqZVhOaTRsN2t4L3lWRw$UJbZAJcm7ujhLv/Y/DcyMDqD42ME95l+Cd5Kh2XJ2G0|0
Ensuite dans le formulaire de connexion, l’utilisateur sera redirigé vers un formulaire de réinitialisation de mot de passe si son mot de passe n’a pas été changé, sinon il peut naviguer de manière classique.

Ici, nous sauvegardons dans la session l’identifiant ou le nom de l’utilisateur après avoir validé la connexion.
Enfin, le code pour traiter le formulaire de changement de mot de passe sera le suivant :

Note : Ici nous vérifions uniquement que l’ancien mot de passe est différent du nouveau. Dans l’idéal, il faudrait aussi vérifier que le mot de passe respecte une bonne politique.
Dans ce cas de figure, la migration des données est plus complexe, car les mots de passe ne sont pas identifiables. Ici, ce que nous conseillons est de faire de l’Argon2id directement sur le hash stocké en base de données.
Pour notre exemple, nous nous plaçons dans un cas ou le hash md5 est utilisé, avec du sel et du poivre pour être dans le cas le plus complexe possible.
Les mots de passe sont donc stockés en base de cette manière : md5 (sel + mot de passe + poivre).
La connexion aura donc cette forme :

Le code de conversion sera le même que l’exemple précédent. En effet c’est directement le hash md5 qu’on veut encoder, valeur qui est déjà présente dans la base de données (colonne password).

Le formulaire de connexion va cependant être plus complexe, car nous avons plusieurs conditions à valider :
isUpdatePassword vaut false, alors l’utilisateur n’a pas changé son mot de passe et le hash sera : argon2id [md5 (sel + mot de passe + poivre)].isUpdatePassword vaut false alors, l’utilisateur a changé son mot de passe et le hash sera : argon2id (mot de passe + poivre). 
Et le formulaire de reset sera le suivant :

Les algorithmes considérés comme fiables pour stocker les mots de passe ont un point positif : ils peuvent s’adapter avec le temps et l’évolution du matériel en augmentant leur cout. Il peut donc être pertinent d’anticiper l’évolution des algorithmes dans votre formulaire de login.
En PHP vous pouvez utiliser la fonction password_needs_rehash.
Auteur : Thomas DELFINO – Pentester @Vaadata