I. Introduction▲
I-A. L'importance de hasher les mots de passe▲
Stocker un mot de passe ne se fait pas de n'importe quelle façon et surtout pas en clair. C'est certes très pratique, car on peut redonner facilement son mot de passe à un utilisateur mais ce n'est pas sécurisé ! Si une personne malveillante accède à vos bases de données, il accède à toutes les informations des utilisateurs. Et comme un utilisateur utilise très souvent le même mot de passe partout (grosse erreur), vous offrez accès à bien plus que votre propre site.
Hasher les mots de passe est donc indispensable !
Note : on ne confondra pas cryptage et hashage, le premier est réversible, pas le second (en théorie).
I-B. Les solutions historiques▲
Quand on parcourt le web à la recherche de tutoriel, on vous conseille généralement l'utilisation de MD5 pour hasher les mots de passe.
$hash
=
md5('
Mot de passe
'
) ;
C'était une solution intelligente il y a 10 ou 15 ans, aujourd'hui il existe des tonnes de tables de comparaison (rainbow table) pour MD5 ce qui le rend quasi inutile pour tous les mots de passe issus du dictionnaire. Sans compter que les performances actuelles des CPU et GPU rendent le brute force relativement rapideBenchlark d'outil de bruteforce.
La mode de MD5 passée, c'est SHA qui a pris la suite. Bien meilleur que MD5, on peut encore l'utiliser aujourd'hui notamment si on lui associe un sel pour rendre inutile les rainbow tables.
$hash
=
sha1('
Mot de passe
'
) ;
I-C. Bonnes pratiques▲
Vous l'aurez compris, stocker un mot de passe en clair est une faute grave et ne se justifie jamais. Votre mot de passe est une donnée ultrasensible et ne doit être connu que de vous.
I-C-1. Algorithme▲
Aujourd'hui la référence pour la hashage est Bcrypt basé sur blowfish, cet algorithme possède deux avantages :
- il utilise un sel aléatoire pour créer le hash ;
- il est adaptatif, c'est-à-dire que l'on peut augmenter le nombre d'itérations pour le rendre plus lent et donc s'adapter à la montée en puissance du matériel.
L'inconvénient majeur c'est que les anciennes versions de PHP ne proposent pas de fonction simple pour générer un hash.
Il faut alors passer par la fonction crypt()Documentation et la génération de grains de sel aléatoires, ce qui peut se révéler être assez complexe finalement. Pour les plus curieux vous pouvez trouver un exemple d'implémentation de Bcrypt dans Pry, mon framework PHP5.
I-C-2. Comment récupérer un mot de passe ?▲
Pour faire simple, on ne peut pas et on ne doit pas pouvoir !
Si un utilisateur perd son mot de passe on lui demande de le réinitialiser avec un nouveau mot de passe. On s'assure en général de l'identité de l'utilisateur via l'envoi d'un lien par mail permettant d'activer la réinitialisation du mot de passe.
Un site qui vous permet de retrouver votre mot de passe est un site non sécurisé.
Soit le site ne protège absolument pas votre mot de passe, il est donc visible sans le moindre effort à toute personne ayant accès à la base.
Soit le site a mis en place un cryptage (réversible). Pour les mots de passe, le cryptage est une fausse bonne idée puisque l'administrateur du site qui possède la clé de décryptage peut avoir accès à vos informations. De plus la clé de décryptage est en général sur le serveur, un hacker pourrait la trouver, et donc décrypter toutes les données cryptées.
I-C-3. Résumé▲
Pour être sécurisé il faut donc respecter ces points :
- pas de mot de passe en clair ;
- pas de mot de passe crypté ;
- mot de passe hashé avec Bcrypt.
La nouvelle API pour la gestion des mots de passe de PHP intègre Bcrypt, et nous permet d'appliquer simplement le principe le plus important dans la gestion des mots de passe : le hashage !
II. Hasher un mot de passe▲
PHP met à disposition la fonction password_hash()Documentation pour hasher les chaînes de caractères. L'utilisation la plus simple mais non recommandée est :
$pass
=
'
Mot de passe
'
;
$hash
=
password_hash($pass
,
PASSWORD_DEFAULT) ;
Cette utilisation est non recommandée, car non pérenne. La constante PASSWORD_DEFAULT fait référence à l'algorithme par défaut défini dans PHP, algorithme qui est susceptible de changer d'une version à l'autre. On préférera donc spécifier l'algorithme à utiliser :
$pass
=
'
Mot de passe
'
;
$hash
=
password_hash($pass
,
PASSWORD_BCRYPT) ;
À l'heure actuelle seul PASSWORD_BCRYPT est disponible, mais d'autres pourraient faire leur apparition et être listés sur la page dédiéeListe des contantes.
Le troisième paramètre de la fonction de hash permet de spécifier des options pour l'algorithme, comme le sel ou la complexité :
$pass
=
'
Mot de passe
'
;
$hash
=
password_hash($pass
,
PASSWORD_BCRYPT,[
'
cost
'
=>
13
]
) ;
Dans cet exemple on augmente le coût de l'algorithme le rendant par la même occasion plus lent et donc plus dur à « brute-forcer ».
Il existe également l'option « salt », qui permet de générer ses propres grains de sel. Je vous déconseille de l'utiliser, PHP génère déjà tout seul des sels aléatoires. À moins d'être un expert dans le domaine vous ne feriez que réduire la sécurité du hash.
II-A. Comment déterminer le bon coût▲
Nous l'avons évoqué juste avant, il est possible de spécifier un coût à l'algorithme. Trop faible, le hash s'expose au brute force, trop fort, il mettra trop de temps à être généré.
Pour déterminer le coût idéal en fonction du matériel disponible, on peut utiliser ce code :
function getOptimalCost($timeTarget
)
{
$cost
=
9
;
do {
$cost
++;
$start
=
microtime(true);
password_hash("
test
"
,
PASSWORD_BCRYPT,
[
"
cost
"
=>
$cost
]
);
$end
=
microtime(true);
}
while (($end
-
$start
) <
$timeTarget
);
return $cost
;
}
echo getOptimalCost(0.3
);
On choisira en général un temps d'exécution entre 0,1 s et 0,5 s ; c'est le bon compromis entre sécurité et attente de l'utilisateur.
III. Comparer un mot de passe et un hash▲
Vous l'aurez peut-être remarqué, le hash d'une même chaîne ne donne jamais le même résultat, dans ce cas comment le comparer ?
PHP met à disposition la fonction password_verify()Documentation, pour cela :
IV. Vérifier un hash▲
Comme il est possible de passer des options au hash, il se peut qu'au fil du temps un hash ne soit plus compatible car, par exemple, on a souhaité augmenter le coût de l'algorithme.
Il est possible pour commencer d'avoir les informations sur un hash via la fonction password_get_info()Documentation.
print_r(password_get_info($hash
));
Cela affichera le type d'algorithme ainsi que les options utilisées :
Array
(
[algo] => 1
[algoName] => bcrypt
[options] => Array
(
[cost] => 12
)
)
Pratique, mais il est plus simple d'utiliser directement la fonction password_needs_rehash()Documentation qui va vérifier la validité d'un hash avec les options fournies :
if (password_verify($pass
,
$hash
)) {
if (password_needs_rehash($hash
,
$algorithm
,
$options
)) {
$hash
=
password_hash($pass
,
$algorithm
,
$options
);
}
}
On peut utiliser ce test après avoir changé le coût de l'algorithme pour permettre aux utilisateurs de réinitialiser leur mot de passe de manière progressive, au fur et à mesure qu'ils accèdent au site.
V. Alternative pour PHP < 5.5▲
Si vous ne pouvez pas encore utiliser PHP 5.5 ou que vous devez supporter plusieurs versions de PHP, tout n'est pas perdu.
Il existe de nombreuses alternatives, la plus connue étant très probablement password_compat qui implémente exactement la même API pour des versions de PHP à partir de 5.3.7
VI. Conclusion▲
Plus d'excuse désormais, vous avez tous les outils nécessaires à la sécurisation de vos mots de passe et ce, même avec des versions de PHP plus anciennes.
N'oubliez pas de traiter les données sensibles de vos utilisateurs comme si c'était les vôtres.
VII. Remerciement▲
Merci à christele_r et Bovino pour leur relecture technique ainsi qu'à f-leb pour les corrections.