Aller au contenu

[Snippet #02 - PHP] hasher un mot-de-passe

Différentes solutions pour hasher vos mots-de-passe de façon sécurisée.

Cet article est obsolète

Depuis PHP 5.5, il est plutôt conseillé d'utiliser les fonctions natives de PHP password_hash() et password_verify().

Pour des raisons de sécurités, préférez toujours vous baser sur une approche éculée plutôt que de réinventer la roue par vous-même.

Le hashage est devenu une méthode incontournable pour éviter de refiler tous les mots-de-passe de nos utilisateurs au premier hacker venu (si vous ne savez pas, je vous conseille de vous documenter). Mais quelle méthode utiliser ? Lesquelles sont sécurisées ? Lesquelles sont disponibles sur votre système.

De plus, même si vous mettez en place votre méthode de hashage, vous n'êtes probablement que de simples développeurs. Pas des experts en sécurité, ni en cryptographie. Il y a fort probablement un détail qui va vous échapper, une faille qui va rester...

Solution 1 : "pourvu qu'aucun hackeur ne s'en prenne à mon site"

Voilà ce qu'on faisait auparavant, et qu'il vaut mieux éviter aujourd'hui.

<?php
$password = 'jmlaBiere69';
$hash = md5($password);
//DO: save hash to database

MD5 - au même titre que SHA1, mais aussi SHA2 (SHA256/SHA512) - est prévu pour être rapide à exécuter. Il est donc "aisé" pour une personne mal intentionnée entrée en possession de votre base de données d'attaquer par force brute ces hash pour retrouver les mots de passe. Même si vous salez correctement les chaînes avant de les hasher.

Solution 2 : laisser faire les experts

Pourquoi coder soi-même ce que des gens plus compétents ont pu faire avant nous ? Je vous présente PHPass, une petite librairie simple, légère, optimisée et réputée (utilisée entre autre par Wordpress et phpBB). Elle gère automatiquement le choix des algorithmes et fonctions les plus sécurisées disponibles selon votre installation de PHP et votre système.

Intégrez à votre projet le fichier PasswordHash.php téléchargé sur le site de PHPass, puis utilisez-le comme suit :

<?php
require_once 'PasswordHash.php';
$hasher = new PasswordHash(8, FALSE);

$password = 'jmlaBiere69';
$hash = $hasher->HashPassword($password);
//DO: save hash to database

Note 1 : le sel et le hash sont concaténés. Ainsi, vous n'avez qu'un seul élément à enregistrer dans votre base de données.

Note 2 : si vous ne voulez pas vous casser la tête, laissez les paramètres du constructeur (8, FALSE) ainsi. Sinon, allez jeter un oeil dans la doc.

Pour revérifier le mot de passe (quand l'utilisateur tente de se connecter, par exemple) :

<?php
if( $hasher->CheckPassword($password, $hash) ) {
  //DO: authenticate user
}

Solution 3 : retrousser ses manches

Si on ne veut pas dépendre d'une librairie tierce, il va falloir plonger nos mains dans le cambouis et répondre aux questions suivantes :

  1. Avez-vous accès au hashage par Blowfish ? Vérifiez simplement la condition (defined(CRYPT_BLOWFISH) && CRYPT_BLOWFISH == 1). A partir de PHP 5.3, c'est forcément le cas.
  2. Avez-vous accès à la fonction openssl_random_pseudo_bytes() pour générer des chaines aléatoire de manière plus sécurisée que rand() ou mt_rand() ? Elle fait partie du module openssl de PHP. Vous pouvez vérifier la condition function_exists('openssl_random_pseudo_bytes').

Partons du principe que vous avez bien PHP 5.3, mais que vous n'avez pas accès au module openssl de PHP (configuration assez fréquente).

Pour hasher le mot-de-passe, on va utiliser crypt() (il est aussi possible de passer par hash(), mais pas pour Blowfish). Cette fonction attend la chaine à hasher, ainsi que le sel que nous devons générer nous-même. Le plus tordu : la forme et la composition du sel fourni définira quel algorithme de hashage crypt va utiliser (ex : MD5, SHA1, Blowfish, ...).

Inspiré de ce post dans la doc de crypt() :

<?php
//$password: the clear password to hash
//$cost: number of iteration, from 4 to 31. The higher, the safer, the heavier
//return: the concatenated random salt + hashed password
function blowfishCrypt($password,$cost)
{
    //the 63 different authorized characters for the salt
    $chars='./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

    //the salt prefix that will define which hashing algorithme crypt() will call (Blowfish)
    if (version_compare(PHP_VERSION, '5.3.7') < 0) {
        $salt=sprintf('$2a$%02d$',$cost);
    } else {
        $salt=sprintf('$2y$%02d$',$cost);
    }

    //the salt must be 22 characters long
    for($i=0;$i<22;$i++) $salt.=$chars[mt_rand(0,63)];

    return crypt($password,$salt);
}

EDIT (2013-03-16) : une petite librairie intéressante génère des chaines aléatoires pour vous de manière beaucoup plus sécurisée que le mt_rand() de l'exemple ci-dessus. Jetez un oeil à la librairie ou directement à l'utilisation que j'en fait dans YosLogin.

Conclusion

Le choix est votre. La première solution est toujours valable lorsque vous agissez sur des données peu sensibles. La seconde conserve le meilleur des deux mondes : simplicité et sécurité. La troisième reste la plus modulable, mais n'oubliez pas que vous n'êtes pas forcément un expert, et que vous risquez de commettre des erreurs.

Bibliographie (en vrac, beaucoup de redites, mais tous ces liens sont intéressants) :

Keep hashing & salt on!

(ça ne veut absolument rien dire, mais je trouve que c’est assez dans le ton).