Comment modifier des mots de passe pour sécuriser leur stockage avec Argon2 ?

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 ?

Quelles sont les recommandations sur le stockage sécurisé des mots de passe ?

Avant d’entrer dans le vif du sujet, quelques précisions sur les recommandations de l’OWASP sur le stockage des mots de passe  :

  • Utiliser Argon2id avec une configuration minimale de 19 MiB de mémoire, un nombre d’itérations de 2, et 1 degré de parallélisme.
  • Si Argon2id n’est pas disponible, utiliser Scrypt avec un paramètre de coût CPU/mémoire minimal de (2^17), une taille de bloc minimale de 8 (1024 octets) et un paramètre de parallélisme de 1.
  • Pour les anciens systèmes utilisant Bcrypt, utiliser un facteur de travail de 10 ou plus et avec une limite de mot de passe de 72 octets.
  • Si la conformité FIPS-140 est requise, utiliser PBKDF2 avec un facteur de travail de 600 000 ou plus et une fonction de hachage interne de HMAC-SHA-256.
  • Utiliser un poivre pour fournir une défense supplémentaire en profondeur (bien que seul, il n’apporte aucune caractéristique de sécurité supplémentaire).

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 :

  • Les mots de passe sont stockés en clair ou avec un algorithme de chiffrement.
  • Les mots de passe sont stockés avec un hash inadapté (md5, sha1, sha2, sha3…).

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.

Les mots de passe sont stockés en clair ou avec un algorithme de chiffrement

Modifier les mots de passe en base de données avec le hash Argon2id

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 :  

IDNamePassword
1adminazerty
2totomatrix
3billyyep59f$4txwrr
4tatamatrix
5titifreepass
6attaquanttesPwndPassword

…en :

IDNamePassword
1admin$argon2id$v=19$m=65536,t=4,p=1$UjYzcS42QmhmLjFsa3lrYQ$7t8mfl
Th2JhcVqSTdQ0GwtLtMh6plWECubPEH8NjEUM
2toto$argon2id$v=19$m=65536,t=4,p=1$SURlcDNVUDZFWEVlQy5UYg$mv
m0Iohc9nd/KOLP5kAw6/WB+PAK0Nt6QGPTsdQa8aw
3billy$argon2id$v=19$m=65536,t=4,p=1$WTlMMDJDeUVMeDNONXRDaQ$u
ccT7LUNmJcj8+ZLqtT+U7m+IJePgCuvv8BxCzRYKG4
4tata$argon2id$v=19$m=65536,t=4,p=1$c00udFlHMm1LMXhwcDBjVg$NR0I
QiLqC0dMSgYHbtEhcAGdrFqhdI4YoOTjtMW1zZA
5titi$argon2id$v=19$m=65536,t=4,p=1$U1Y2dm44cXlMWHp0SkFhVg$lYj9k
Lfx6zUNOTDUXHDhpEFyBdSJXDv9qjxA0+4oEYw
6attaquant$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.

Vérifier la validité des mots de passe utilisateurs

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.

Les mots de passe sont stockés avec un hash inadapté (md5, sha1, sha2, sha3…)

Migrer les hash vers Argon2id

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 :

  • À la première connexion valide de l’utilisateur, lui demander un nouveau mot de passe et stocker uniquement l’argon2id de ce mot de passe
  • Devoir gérer 2 cas dans le formulaire de connexion :
    • Si la colonne 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)].
    • Si la colonne 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 :

Si vous utilisez des hash adaptés

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