Stocker des mots de passe dans une base de donnee
Si vous êtes responsable d'un site internet où les utilisateurs doivent s'identifier, il est probable que vous deviez stocker leurs mots de passe dans une base de données.
Pour cela, il existe plusieurs solutions dont souvent la simplicité est inversement proportionnelle à la sureté.
Le risque
Pour mieux comprendre comment sécuriser, il faut d'abord comprendre quels sont les risques.
Brute Force :
La manière la plus simple, barbare et efficace si l'on possède du temps et un ordinateur suffisamment puissant.
Il suffit d'essayer toutes les combinaisons possibles de mots de passe jusque tomber sur la bonne.
Notez qu'il est assez simple de s'en protéger en ajoutant un captcha (attention : chiant pour les utilisateurs, empêche les mal-voyants d'accéder à votre site) ou en faisant un compteur d'essai (via une variable de session, conseillé).
Accès à la base de données :
Que ce soit parce que l'attaquant à trouvé votre mot de passe, via une faille, une injection SQL ou n'importe quoi d'autre, il faut envisager la possibilité que l'attaquant accède au contenu de votre base de données. Dans le cas où il ne modifie pas le contenu, autant être sûr qu'il ne puisse pas trouver les mots de passe de vos utilisateurs trop facilement.
Solutions
1. Stocker en clair
Idée naïve que l'on a en premier lieux si l'on ne pense pas au fait que l'attaquant puisse accéder à la base de données.
Est à éviter absolument dans n'importe quelle situation !
Vous êtes peut-être quelqu'un d'intègre qui ne profitera pas du fait de connaitre les mots de passe de vos utilisateurs (souvent les gens utilisent le même mot de passe pour plusieurs comptes), mais il n'en est pas la même chose de tout le monde.
Malheureusement, cette solution est encore utilisée à de nombreux endroits. Un certain nombre de sites internet vous proposent de vous renvoyer votre mot de passe si vous l'avez oublié, cela implique qu'il n'a pas été haché et c'est une grosse faille de sécurité potentielle.
Il y a peu skyrock a subi une attaque où il serait possible que les attaquants aient mis la main sur les mots de passe du site (plusieurs millions de comptes) qui seraient stockés en clair.
Il y a 4 ans, c'est Reddit qui a avoué avoir perdu un backup de sa base de donnée avec toutes les info et mots de passe en clair de ses utilisateurs.
Même les gros sites font des conneries. Ne faites pas la même chose.
2. MD5
Plutôt que de les stocker en clair, il est conseillé d'utiliser un hash (de l'anglais to hash) du mot de passe, c'est-à-dire transformer, via des procédés mathématiques trop compliqués pour moi, transformer une phrase en une suite constante de caractère sans qu'il soit possible de faire l’opération inverse (fonction à sens unique). Cette méthode est très efficace, car l'attaquant ne trouvera dans votre base de données que des infos du style "login:admin ; pass:ab7z25t3d9" où "ab7z25t3d9" est le hash de mon mot de passe et non celui que je rentre dans le formulaire.
(note : les fonctions de hachage sont à ne confondre avec les fonctions de chiffrement qui permettent de retrouver le mot chiffré à partir d'un mot de passe. Cette méthode n'est pas beaucoup plus sûre que le stockage en clair et je n'en parle pas ici.)
exemple de code de connexion :
[cc lang="php"]<?php
\$login_post = \$_POST['login'];
\$pass_post = md5sum(\$_POST['pass']);
\$pass_db = getPassword(\$login_post); // fonction qui vous renvoit le hash associé au login dans la base de donnée
if (\$pass_db == \$pass_post)
{
// correct, connexion
}
else
{
// incorrect, retourne une erreur
}
?>
[/cc]
Attention, code très simplifié ! Il faut absolument vérifier que les données sont valides (pas de champs vides, tentative d'injection...) avant.
Le problème des fonctions de hachage est qu'elles entrainent des collisions. Ça semble logique, elles transforment une suite de caractère de longueur non déterminée (infinité de possibilités) en une suite fixe de caractères (nombre fini de possibilités).
Une bonne fonction entraine le moins possible de collision et surtout empêche de les trouver.
Seulement, ce n'est pas le cas du md5 qui depuis quelques années déjà a été cassé et permet de trouver rapidement des collisions.
Cela veut dire qu'à partir de la suite de 32 caractères produite, je peux trouver une autre suite qui n'est pas forcement le mot de base, mais produit le même hash et donc fonctionnera dans notre formulaire.
De plus, il existe ce que l'on appelle des rainbow table qui sont de gigantesques bases de données stockant un grand nombre de hash possible et permettant de retrouver en quelques secondes les hashs les plus communs (dans tous vos utilisateurs, il y en aura bien un pour utiliser "1234" ou "password" comme mot de passe).
Un exemple de rainbow table de plus de 500 millions de hash.
3. Autres fonctions
Comme le md5 a été cassé, on peut imaginer utiliser une fonction de hachage un peu plus complexe.
La première qui vient à l'esprit est souvent sha1 mais il en existe beaucoup d'autres sha256, whirlpool, RIPEMD-160,...
[cc lang="php"]<?php
\$login_post = \$_POST['login'];
// différentes fonction de hash
\$pass_post = sha1(\$_POST['pass']);
\$pass_post = hash('sha256',\$_POST['pass']);
\$pass_post = hash('ripemd160',\$_POST['pass']);
\$pass_post = hash('whirlpool',\$_POST['pass']);
// ...
// liste sur http://www.php.net/manual/fr/function.hash-algos.php
\$pass_db = getPassword(\$login_post); // fonction qui vous renvoi le hash associé au login dans la base de données
...
?>
[/cc]
Cependant, elles ne sont pas sûres non plus. Sha1 n'a pas encore été cassé mais on s'attend à ce qu'il le soit bientôt, il est donc a éviter.
sha256 et compagnie résiste encore, mais rien ne garantit pour combien de temps.
Il a été prouvé qu'elles étaient beaucoup moins sensibles aux collisions, mais ce n'est pas le seul problème.
Les rainbow table encore une fois sont un risque à ne pas négliger. Il y aura toujours des utilisateurs qui utiliseront "1234" comme mot de passe et quelque soit l'algorithme utilisé, ils ne tiendront pas longtemps...
Il existe malheureusement de très nombreux sites utilisant md5 sans se poser de question pourtant ce n'est pas beaucoup plus sûr qu'en clair.
4. Salt
Pour lutter contre le maillon faible de la chaine de l’authentification (la stupidité de l'utilisateur), on peut manuellement renforcer la sécurité en rajoutant une suite de caractère pour rallonger le mot de passe.
[cc lang="php"]<?php
\$pass = 'aZk!@14x32=' . \$_POST['pass'];
\$pass_post = hash('sha256',\$pass);
?>[/cc]
L'utilisateur utilisant "abc" comme mot de passe, se retrouvera donc avec "aZk!@14x32=abc", ce qui a déjà beaucoup moins de chance de se trouver dans une rainbow table.
Il est possible de compliquer en l'insérant au milieu.
[cc lang="php"]<?php
\$pass = 'aZk!@' . \$_POST['pass'] . '14x32=';
\$pass_post = hash('sha256',\$pass);
?>
[/cc]
Un problème est que deux personnes ayant le même mot de passe auront le même hash. Un pirate ayant créé un compte avec "1234" comme mot de passe pourra rapidement voir tous ceux qui ont le même hash que lui et donc connaitra leur mot de passe aussi compliqué le salt soit il.
5. Différencier les utilisateurs
Pour éviter que deux utilisateurs avec le même mot de passe aient le même hash, une solution est d'ajouter le login qui est unique.
[cc lang="php"]<?php
\$login_post = \$_POST['login'];
\$salt = 'aZk!@14x32=';
\$pass_post = hash('sha256',\$login_post.\$_POST['pass'].\$salt);
?>
[/cc]
Ou avec un simple salt spécifier une position choisie aléatoirement pour chaque utilisateur au moment de l'inscription et stockée dans la base données.
[cc lang="php"]<?php
\$login_post = \$_POST['login'];
\$infos = getInfo(\$login_post);
\$salt = 'aZk!@14x32=';
\$part1 = substr(\$salt, 0, \$infos['position']);
\$part2 = substr(\$salt, \$infos['position']);
\$pass_post = hash('sha256',\$part1.\$_POST['pass'].\$part2);
?>
[/cc]
Une autre idée serait d'utiliser un salt différent pour chaque utilisateur stocké lui aussi dans la base de donnée.
[cc lang="php"]<?php
\$login_post = \$_POST['login'];
\$infos = getInfo(\$login_post);
\$pass = \$infos['salt'] . \$_POST['pass'];
\$pass_post = hash('sha256',\$pass);
?>
[/cc]Ou évidement combiner ces méthodes.
Pour compliquer (c'est même conseillé de le faire) vous pouvez aussi insérer le salt et position directement dans le hash.
Par exemple, vous stocker dans votre table \$hash.\$salt.\$position en sachant que le hash fait 64 caractère, le salt 20 et la position 2, il suffit d'un peu de manipulation de string. Ou mieux les 2 premiers caractères sont la position du salt dans le hash qui a une taille fixe.
[cc lang="php"]<?php
\$salt = "2222222222";
\$hash_pass = "00000000000000000000";
\$position_salt_hash = "07";
\$position_salt_pass = "14";
\$hash_db = "07"."0000000"."2222222222"."14"."00000000000000";
// en stockant \$hash_db dans votre bdd, vous pouvez facilement retrouver toutes les info
// et lors de l'identification, vérifier que
// hash('fct', insert_substr(\$_POST['pass'],"2222222222",14)) est égal à "00000000000000000000"
?>
[/cc]
Soyez imaginatif, ça compliquera la tâche de l'éventuel pirate.
6. Ralentir les requêtes
Le problème de ces solutions est que lorsque le pirate connait comment vous hachez vos mots de passe, il lui est toujours possible de générer une rainbow table spécifique à votre cas.
Aussi surprenant que ça puisse paraitre, le problème des algorithmes de hachage classiques est qu'ils sont optimisés pour être rapides. Lorsque l'on parle de chiffrement de mot de passe, l'on ne veut PAS être rapide.
Un pirate avec une machine assez puissante peut générer en quelques minutes/heures/jours une table de plusieurs millions d'éléments.
Des fonctions telles que sha512 sont certes plus lentes que md5 mais toujours très (trop ?) rapides.
Pour lutter contre cela, il existe un programme appelé bcrypt qui utilise la méthode de chiffrement de Blowfish en permettant de choisir le temps d'exécution.
Pour vous et l'utilisateur, quelle différence y a il entre 0.000001 et 0.1 seconde pour générer le mot chiffré ? Aucune, ça reste très rapide dans les deux cas.
Pour le pirate ? 10.000 fois moins de mot de passe généré, 10.000 fois plus de temps nécessaire...
[cc lang="php"]<?php
// disponible depuis PHP 5.3
// execution en 0.09 seconde sur mon pc
// a l'inscription
\$salt = hash('sha256',microtime()."this is a simple but still hard to guess salt");
\$pass_post = crypt(\$_POST['pass'],'\$2a\$10\$'.\$salt.'\$');
// au login
\$hash_db = getPass(\$_POST['login']);
if (crypt(\$_POST['pass'],\$hash_db) == \$hash_db)
...
?>
[/cc]
Pour ceux que ça intéresse, lisez cet article
Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes
7. Ne PAS stocker les mots de passe
Mais au fond pourquoi voulez-vous stocker les mots de passes de vos utilisateurs ? Pourquoi ne pas laisser quelqu'un d'autre le faire ?
Google, Facebook et d'autres vous permettent de vous connecter en utilisant un compte de chez eux.
Vous n'avez confiance en eux ? Je vous comprends, moi non plus, utilisez plutôt OpenId qui fonctionne sur un modèle décentralisé.
Nombre de sites (les deux cités précédemment mais aussi myopenid, myID, ClaimID et autres) vous permettent d'utiliser votre identité sous la forme d'une url pour vous connecter sur tous les sites le proposant.
De cette façon, vous laissez le stockage de mot de passe à la charge du service que l'utilisateur aura choisi.
Pour finir
Pour finir quelques remarques.
- Il n'existe pas de méthode totalement sûre aujourd'hui et encore moins qui le seront toujours demain.
- N'agissez pas que sur base que votre site n’intéresse personne et que personne n'essayera de le pirater.
- La plupart des faiblesses se basent sur des scénarios catastrophes où l'attaquant à accès à beaucoup de ressources. Scénarios catastrophes mais qui arrive même aux grands sites...
- Faites en sorte que l'utilisateur soit le moins possible responsable de sa sécurité (c'est bien connu, les gens sont cons et la démocratie est une très mauvaise idée)
- Ne jouez pas les experts en essayant de bidouiller quelque chose, laissez ça aux pros.
- Le fait d'utiliser plusieurs fois de suite la même méthode (ex: md5(md5m(\$pass))) ne renforce pas la sécurité, au contraire des mathématiciens ont montré que cela avait l'effet inverse.
- Faire confiance aux mathématiciens en ce qui concerne la cryptographie (toujours bien de se répéter)
- Ajouter des astuces basiques perso (salt, bourrage pour faire passer un sha256 pour un sha512,...) ne font pas perdre de sécurité, vous protège des attaques "classiques" mais retenez qu'une sécurité basée sur un secret est vouée à l'échec.
- N'oubliez pas que ce n'est qu'un aspect de votre sécurité. Il existe de très nombreux autres aspects de la sécurité auquels il faut penser.