La sécurisation des mots de passe, c’est salée !

Publié dans Geekeries | Marqué avec ,
Share

Dans un précédent billet (Comment sécuriser les mots de passe de mes utilisateurs), j’expliquais quelques bases pour bien comprendre comment stocker convenablement le mot de passe de ses utilisateurs. Comme on l’a vu, il suffit d’enregistrer la valeur hachée du mot de passe (avec une bonne méthode de hashage comme SHA-1 ou SHA-2), pour éviter les conséquences désagréables sur la sécurité d’un vol de base de données !
Aujourd’hui, nous allons aller encore plus loin en tentant de contre-carré les attaques par dictionnaire ou pire par rainbow tables ! Pour cela, on aura besoin d’une bonne technique de salage (oui, avec du sel !), voire de poissons volants (blowfish >) !

Pourquoi un hashage simple est insuffisant

Mots de passe « faibles »

J’espère avoir le temps d’en reparler, mais afin de comprendre pourquoi un hashage simple de mots de passe est insuffisant, il faut avoir en tête qu’un pourcentage conséquent de vos utilisateurs choisiront des mots de passe faibles ! Par exemple : password1, abc123, myspace1, … comme le montre cette Etude sur les mots de passe les plus courants.
D’où l’importance d’une politique de fiabilité des mots de passe, mais c’est un autre sujet !

Eh, on parle d’une base de données de mots de passe ici !

Si l’utilisateur a accès à votre base de données (oui, c’est à sécuriser en premier lieu ;-)), cela veut dire qu’il peut faire ses petits calculs sur tous vos mots de passe, et donc les plus faibles vont tomber en premier ! Et puisque vous avez lu le paragaphe précédent, vous savez que certains mots de passe sont très faibles !
Par contre, si l’attaquant vise quelqu’un en particulier (vous par exemple), c’est bien plus difficile ! Connaissez-vous le paradoxe des anniversaires ?

Dictionnaire de mots de passe

Mais comment un attaquant ayant accès à votre base de données, va-t-il retrouver un mot de passe ? Il peut :

  • Tester toutes les combinaisons possibles. Bon courage 😉
  • Utiliser un dictionnaire des mots de passe les plus courants, pour tester si l’un de ces mots, haché, correspond à un des mots de passe de votre base de données. Se procurer, ou créer un de ces dictionnaires n’est pas très difficile.
  • Pire, utiliser une rainbow table, c’est-à-dire (en bref) un dictionnaire des mots de passe (automatiquement généré), mais déjà hachés avec SHA-1 ou SHA-2 ou autre ! Évidement, c’est la meilleure des solutions !

Bref, si l’attaquant a accès à votre base de données de mots de passe, il va très rapidement trouver un des mots de passe (sûrement le plus « faible »), et c’est déjà fichu ! Pire, sans autres mesures de protection, un attaquant pourrait même tenter un « brute force » sur votre formulaire d’authentification, et tenter ainsi de trouver votre mot de passe ! C’est moins facile à cause de la « lenteur » des requêtes HTTP, et du fait qu’il ne peut viser qu’un seul utilisateur à la fois, mais ça peut marcher…

Heureusement, le salage va nous aider à corriger (en partie) le premier problème, et à rendre plus difficile le second.

France Noirmoutier Sel brut

Première solution : le sel !

Le principe consiste à associer à chaque utilisateur une valeur aléatoire (par exemple : sa date d’inscription) que l’on sauvegarde. On appelle cette valeur aléatoire : le sel ! Quant au mot de passe, on lui ajoute ce sel, on hashe le tout, puis on sauvegarde !
Puisque ce sel est différent pour chaque utilisateur, l’attaquant doit maintenant créer une rainbow table spécifique pour chaque utilisateur, puisqu’il doit à chaque fois prendre son dictionnaire de mot de passe, y ajouter le sel d’un utilisateur, et le hasher. Et ça, c’est long ! Très long. Trop long !

Il n’est pas nécessaire de sécuriser le « sel » de chaque utilisateur. Certains insistent pour que la valeur utilisée soit suffisamment aléatoire, mais je ne vois franchement pas pourquoi puisque le résultat souhaité est atteins même une utilisant une simple data d’inscription. Bref, à méditer…
Mise à jour du 27 février 2013 : Après méditation (enfin surtout grâce au commentaire de JeromeJ), il semble bon en effet d’utiliser un sel assez aléatoire. Pour deux raisons :

  • si tout le monde utilise une date comme valeur pour le sel, eh bien il existe sûrement des algorithmes optimisés pour créer des dictionnaires avec des dates (on verra ça dans le prochain article, une histoire d’arc en ciel).
  • ça ne nous coûte pas grand chose 😉 (et ça coûte bien plus à l’attaquant !)

En résumé :

Stockage

  • Lors de l’inscription de l’utilisateur on créé le sel : sel = valeur aléatoire
  • On stocke dans la base de données : sel, hash(sel + mot de passe)
    • Exemple : sel = 8644jhg46j8, password = SuperPassw0rd.
    • On stocke : "8644jhg46j8", hash_sha2("8644jhg46j8"+"SuperPassw0rd").

Authentification

  • On récupère le sel de l’utilisateur à authentifier, et on l’ajoute au mot de passe qu’il nous a donné.
  • On hashe, et on compare tout ça avec le hash préalablement stocké dans la base.
  • Et on sait si le mot de passe est ok ou non !

Un petit exemple d’implémentation en PHP

Définissons d’abord nos fonctions pour hasher et vérifier. Vous remarquerez qu’il est possible de configurer l’algorithme de hashage à utiliser, et que je respecte l’interface de méthodes qui seront sûrement rajoutées dans PHP 5.5 (PHP The right way – password hashing). J’ai toutefois ajouté le « sel » en paramètre des fonctions pour des raisons pédagogiques 🙂

// Dans un fichier de configuration
define('FonctionDeHachage', 'sha512');
// Fonction de hashage
function password_hash($password, $salt) {
	$hash = hash(FonctionDeHachage, $password.$salt);
	return $hash;
}
// Fonction de vérification
function password_verify($givenPassword, $salt, $existingPasswordHash) {
	$hash = hash(FonctionDeHachage, $givenPassword.$salt);
	return ($hash === $existingPasswordHash);
}

Maintenant, utilisons tout cela pour stocker le mot de passe lors de l’inscription d’un utilisateur :

// Data
$username = 'Fylhan';
$newPassword = 'SuperPassw0rd';
$salt = mt_rand(1, 1000);
// Hash
$passwordToBeStored = password_hash($newPassword , $salt);

// Store data: $username, $salt, $passwordToBeStored 

Et vérifier le password de l’utilisateur lorsqu’il s’authentifie :

// Data
$username = 'Fylhan';
$goodPassword = 'SuperPassw0rd';
$badPassword = 'Beurk';

// Retrieve $salt for user 'Fylhan'

// Verify goodPassword
if (password_verify($goodPassword, $salt)) {
	echo 'Welcome home!';
}
// Verify badPassword 
if (!password_verify($badPassword , $salt)) {
	echo 'Get lost!';
}

Conclusion

Voilà, vous pouvez respirer, vos mots de passe sont correctement sécurisés ! Enfin presque. Ou plutôt : pas du tout ! J’ai déliré avec les « poissons volants » dans mon introduction, et ce n’était pas pour du beurre salé (veuillez excuser ce jeu de mot bien fade) ! Rendez-vous au prochaine épisode pour en savoir plus…

Cet article est la deuxième partie d’une série sur la sécurisation des mots de passe :

(Beaucoup) plus d’informations

8 réponses à La sécurisation des mots de passe, c’est salée !

  1. Ping : Sécuriser les password des utilisateurs Hashage - 30 minutes par jour

  2. Ping : La sécurisation des mots de passe et les poissons volants 30 minutes par jour

  3. Un petit mot pour parler des sites qui permettent à l’utilisateur de recevoir son mot de passe en clair par mail.
    Cela implique une routine « maison » qui crypte de façon reversible.
    Probablement moins sécurisant que la hashage+salage ;o) mais beaucoup confortable pour l’utilisateur.
    Stratégie à moduler en fonction de ses besoins.
    Une anecdote pour finir ; j’ai un ami qui travaille dans une très grosse structure et à qui on impose de changer ses mots de passe chaque mois.
    Or il utilise 4 services différents et le système lui interdit d’utiliser le même mot de passe pour tous les services. Cerise sur le gâteau, quand il change son mot de passe le système détecte quand le nouveau ressemble trop à l’ancien et le bloque!
    Conséquence inévitable : tout le monde dans sa boite écrit ses mots de passe dans un petit carnet caché dans un tiroir de son bureau !!

    • « Cela implique une routine « maison » qui crypte de façon reversible. »
      Et encore tu es gentils ! C’est plus souvent tout simplement en clair.

      A part ça, excellente l’anecdote ! J’ai commencé à écrire un article sur les politiques de gestion des mots de passe. Est-ce que je pourrai réutiliser ton exemple s’il te plait ?

  4. Si beaucoup de gens utilisaient la technique du sel qui correspond à la date d’inscription (avantage, c’est déjà une info qu’on stock en général) plutôt qu’un truc random, alors il existerait déjà plus de rainbow table selon certaines dates. Certes, il existe beaucoup de dates différentes, mais quand même.
    Ce pourquoi je pense que, ça ne coûte pas grand chose de générer un salt bien aléatoire juste au cas où 😉

    MAIS comme tu l’as souligné, le pire reste quand même les mots de passes stupides, c’est un vrai problème de société selon moi.
    Et, des solutions pas trop compliquées mais efficaces existent selon moi : http://olissea.com/doc/?artId=12

    • Ta remarque sur le sel aléatoire me paraît très pertinente. Et comme tu dis « ça ne coûte pas grand chose » ! Voilà qui me convainc, je modifie l’article en conséquence 😉

      Et pour le problème des mots de passe trop faibles, je vais lire ton article avec attention ! J’engrange petit à petit pas mal de ressources à ce sujet, et je compte bien publier un « pot-pourri » (comme on dit) un de ces quatre.

  5. gloubiboulga

    Bonjour/Bonsoir,

    il me semble qu’il y a une erreur, enfin pas vraiment une erreur, mais plutôt deux logiques utilisées à l’intérieur de chacune des deux fonctions password_hash() et password_verify() !

    En effet, dans la fonction qui hache le mot de passe lors de l’inscription qui sera stocké, elle additionne le mot de passe et le sel pour hacher le tout;
    Or dans la fonction qui est chargée, lors de la connexion d’un utilisateur, de vérifier si le mot de passe envoyé correspond à celui déjà stocké, elle concatène ce mot de passe envoyé et le sel pour hacher cette chaîne afin de la comparaître au hash stocké !

    Le résultat haché de l’addition d’un mot de passe X et d’un sel Y ne sera jamais le même que le résultat haché de la concaténation du même mot de passe X et du même sel Y.

    • Merci pour cette lecture attentive ! C’était une erreur et elle est désormais corrigée !

      password_hash a bien été ajouté à PHP 5.5 et il existe des moyens de l’utiliser en PHP < =5.4. Mon petit exemple reste utile pour comprendre le principe, mais plus pour une utilisation quotidienne.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*