Hello tout le monde,

il y a peu on m'a dit que mon hashage était "pas du tout optimisé", je sais pas si c'est vrai, jugez par vous meme :

sha1( sha1($password . $salt) . sha1($salt) );

Pour moi ça pose pas de probleme,

Donc je me suis un peu renseigner notament ici http://php.net/manual/fr/faq.passwords.php et la on me sort, password_hash() ou crypt(), j'ai pas compris le delire de mettre le salt dans la string finale, je me doute bien que c'est pour pas avoir le meme salt partout mais ca doit pas etre compliqué à bruteforce...

Donc je me demandais, comme dit le titre, comment hashez vous vos passwords ?

Merci

raoh

17 réponses


quand tu veux faire simple :

//   Une clé générée aléatoirement que tu gardes pour tout ton script
$key = 'randomKey';
sha1($key . sha1($password) . $key);

Ma méthode c'est de rajouter au password, un salt et un élément du user, ça peut être l'id, le pseudo ou l'email.
l'idée c'est que 2 users qui ont le même password n'aient pas le même hash

sha1($id . $password . $salt)

je ne vois pas l'intérêt de faire 2 sha1

Je te conseille de te mettre à utiliser des fonctions de hachage telle que bcrypt, plutôt que de bricoler quelque chose.
Depuis PHP5.5, tu as disposition password_hash() qui utilise bcrypt par défaut !
Le salt est présent dans la chaîne finale car la sécurité de ton hash ne doit pas reposer sur le fait que le salt soit secret (il est présent dans la base données en même temps que tu condensat, donc il n'est pas vraiment secret…), mais sur l'impossibilité de pouvoir trouver une collision rapidement. Le fait que bcrypt fasse un grand nombre d'itérations ralentit consiréablement le nombre de hashes que tu peux calculer à la seconde (en fonction du facteur de difficulté que tu utilises !) et la présence d'un salt (différent pour chaque hash, ce qui fait que deux condensats peuvent être différents mais le « mot » hashé être le même) permet d'éviter la création de tables pré-calculées de hashs (rainbow tables).

La meilleure façon pour moi de hasher ses mots de passe et de faire un salt
A+ !

base64_encode(password_hash($pwd, PASSWORD_BCRYPT, ['cost' => 11, 'salt' => 'b13nSal3']));

@huggy : justement ! C'est pour cacher la typologie hyper-visible de la commande password_hash en B_CRYPT, puisque son préfixe commence toujours par "$2y$". De plus, si une attaque des mots de passe consiste à modifier quelques éléments de cette chaîne, immanquablement, ça bloquera le password_verify.

Par ailleurs, je ne l'ai pas inventé : regardes les tutos de Lorna Jean , CodeCourse ou Jream : c'est souvent cette technique qui est employée car la plus simple et la plus efficace !

@mzkd tu peux expliquer l'intéret du base64_encode ? car password_hash renvoie déjà une String

Sinon la fonction password_hash comme l'a dit @flan renvoie dans la chaine finale, l'algo + le salt , moi ça me gène un peu !!!
Un pirate a toutes les infos pour générer une rainbow table.
Un salt statique dans le code et non dans la bd est pour moi, plus sûr (on part du principe que le hacker à piraté la bd et pas le code).

@Yoega je pense que t'as un autre pb

Il ne pourra pas générer une table, parce que tu auras un hash par utilisateur (il ne faut PAS fixer le même pour tout le monde, password_hash gère ça tout seul) (donc ça ne servira à rien) et en fonction du coût du bcrypt, tu peux rendre ça suffisamment difficile pour qu'il ne puisse en faire qu'une centaine par seconde, ce qui est ridicule. Et il ne trouvera pas de rainbow table déjà faite pour ton sel aléatoire...
Évite de faire du bricolage sur ce genre de choses, alors que tu as déjà des fonctions qui margent comme il faut proposées dans ton langage. Et après tu pourras te rendre compte que == n'est pas suffisant pour comparer des hashs (il faut utiliser des fonctions comme hash_equals pour éviter les problèmes comme le type juggling ou les timing attacks). Regarde du côté de https://cryptocoding.net/index.php/Coding_rules.

hash_equals c'est fini, il faut utiliser password_verify
l'idée de mesurer le temps pour en déduire la longueur de password , c'est très théorique car via le réseau on mesures des dizaines de ms alors que le décryptage se mesure en microsecondes.
Je reste sur ma position que si un hacker a accès à la bd, il va repérer les comptes admin facilement et va générer les rainbow-table en qques minutes en utilisant le salt offert sur un plateau.
Le bricolage c'est d'utiliser bêtement les fonctions standard.

Non, ce que tu dis est faux. Ces deux fonctions ont des rôles différents. Les deux sont timing-safe mais hash_equals compare deux chaînes (donc les deux condensats que tu donnes) et password_verify hache la première chaîne et la compare avec le condensat passé en argument. Vérifie sur php.net ;-)
De plus, tu ne déduis pas la longueur du mot de passe avec ==, mais le caractère où s'est arrêté la comparaison. Tu réduis donc énormément le spectre de possibilités à tester.
Deuxième chose fausse, tu ne mesures pas des dizaines de ms à travers le réseau, mais des variation beaucoup plus faibles (voir http://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf, We have shown that, even though the Internet induces significant timing jitter, we can reliably distinguish remote timing differences as low as 20us., A LAN environment has lower timing jitter, allowing us to reliably distinguish remote timing differences as small as 100ns (possibly even smaller)).

Il ne va pas pouvoir générer des rainbow tables en quelques minutes puisqu'il pourra au plus générer une centaine de condensats par seconde avec un bon CPU (avec le bon cost de bcrypt). Et vu que chaque condensat aura un sel différent, ce qui est directement géré par bcrypt (ce que toi tu dis ne pas vouloir faire, tu n'as pas dû saisir cet intérêt), générer des tables ne sert à rien - il faut attaquer directement le condensat. Et ça sera tellement lent que si ton mot de passe d'administration n'est pas dans une liste de mots de passe les plus utilisés, il n'y aura pas de soucis. Si il fait partie de ce genre de listes, ça ne sera pas un risque de casser ton hash qui sra le soucis, mais de te faire bruteforcer directement le login…

Cacher son salt n'est PAS la solution (juste http://stackoverflow.com/questions/4807677/why-does-php-crypt-prepend-the-salt-to-the-hash/4808616#4808616 à lire pour s'en rendre compte, unrelated avec la question de SO mais elle détaille le fait que si tu es sûr de pouvoir stocker ton sel de façon safe, pourquoi ne pas y stocker tes mots de passe ? ;D Et tu fixes un sel pour tout le monde, et un accès à la base de données peut donner un accès aux fichiers - en fonction des permissions de l'utilisateur sur le serveur SQL, qui a très souvent les permissions FILE).

Tu restes sur ta position basée sur des concepts et connaissances biaisés, donc me parler « d'utiliser bêtement »…

Pour les temps sur le réseau, fait un simple ping sur Internet et tu me diras comment avoir une précision de 20 microsecondes.
Les 2 fonctions ont le même rôle "comparer la validité du MP" mais les arguments sont différents, certe.
Pour la génération des tables, j'ai bien dit que c'était pour un condensat (celui de l'admin de préférence) et avec 100 hash/s tu en as déjà 360000 en 1 heure.
Sur l'article de StackOverflow il est dit "For best practice you should not publish your salts, in the same way that you should not publish your password hashes.".
A la question "le fait que si tu es sûr de pouvoir stocker ton sel de façon safe, pourquoi ne pas y stocker tes mots de passe ?" parcequ'il faut une bd pour stocker les MP et on part du principe que le hacker y a accès, mais qu'il n'a pas accès au code. autant ne pas tout mettre dans le même panier.
La question est : vaut-il mieux générer un salt par MP et le rendre visible ou bien n'avoir qu'un seul salt pour tous les MP et le cacher.
Ma méthode donnée en début du fil et qui tient compte de l'id du user permet d'avoir l'équivalent d'un salt par user et d'un salt caché. d'ailleurs sur SO on peut lire That's why I recommend a two part salt. One in a config file which is secret but the same for all users, and one part saved together with the password which is different for each user/randomly generated. –
Une autre solution serait d'avoir une seconde bd avec les salts des différents hash, avec bien sûr des comptes différents de la bd principale.
Dans tous les cas si le hacker a accès au code c'est foutu.

@sudovim
Oui je suis d'accord avec ce que tu dis
Je passe mon temps sur les forum et je pense maintenant maitriser
Le seul point sur lequel on diverge, c'est le fait d'avoir un salt par user ou un salt unique mais caché
Sur l'algo Bcrypt, ça fait l'unanimité OK depuis php5.5
Ma solution utilise un salt unique et une partie variable, donc les attaques par Pre-calcul (rainbow table) c'est OK
Pour les attaques brute-force aussi (merci Bcrypt) mais même MD5 réputé faible est très costaud.
Pour les attaques par dictionnaire, seule une partie cachée permet de s'en prémunir
Ici un exemple d'une attaque par dictionnaire (ça affiche 'trouve : 12345')

$le_hash_en_bd = '$2y$10$gPZ9AK0.jIMd70xZJGwrS.xhOsLPn0sz0iH7GaQdKwxjcz9NgEJh.';

$dico = ['123','1234','pupuce','toto','titi','secret','mon_mot2passe','12345'];

foreach($dico as $k => $v) {
    if (password_verify($v, $le_hash_en_bd)) {
        echo "trouve : " . $v;
    }
}

Dans le hash on trouve (séparé par des $) en premier l'algo '2y' = bcrypt
en second '10' c'est le cost (difficulté) et les 22 caractères suivant sont le salt
la fin c'est le hash lui-même
en cachant le salt ou en le stockant ailleurs, on rend l'attaque par dictionnaire plus compliquée.

Qu'est ce que tu trouves de dangeureux dans ma solution ?

C'est plutot interessant comme discussion. C'est pas tellement mon domain, voire même pas du tout mon domaine, mais une question me vient en vous lisant.

Pourquoi partir du principe que l'attaquant a déjà accès a la BDD ?

Je ne comprends pas parce que d'après moi, si il à déjà accès a la BDD, un simple dump et après il a tout son temps pour casser le hash.
D'autant plus qu'il est assez commun de voir des hackers passer par la base de données pour acceder au FTP et donc avoir accès aux fichiers

Si le hacker n'a pas acces à la base, il ne peut que faire des tentatives par le réseau.
Là on parle de comment retrouver un mot de passe à partir de son empreinte qui est dans la base, donc on part du principe que le hacker à réussi à entrer (injection SQL ou autre) ou alors comme tu le dis via FTP, mais peu importe la façon
L'idée c'est de se dire qu'une fois les données récupérées, est-il capable d'en extraire les mots de passe ?
Il a en effet tout son temps une fois les données chez lui.

Slt,
moi perso j'utilise cette solution :

$salt = "N3aFQ6DopPX3YK1jykNPqZq57jG2e5coy9Lq188N2M1kG6rXo8feLh7wUpaOY61d83FLfC88torkUTA2TTr3B4Iqb9u0Z431GX8oQWMX3g9n591sn93rkFTc4y4H19Ce";
$password = "motdepasse";
$password_crypte = sha1(sha1($password).$salt);

(J'ai changer le salt ...)
Ca me donne ceci

470ad0b46211dd3e2dc8d73c41a67276118c4b89 

voila !!!

@Tj_ avec ton algo on repère tout de suite les mots de passe identiques car tu n'as pas mis de partie variable.
regarde les nouveaux API php : password_hash , password_verify et password_needs_rehash
L'idée intéressante c'est de faire évoluer l'algo au cours du temps, tu indiques simplement PASSWORD_DEFAULT comme algorithme
et la fonction password_hash utilise l'algo le plus sûr du moment (bcryp pour l'intant)

$hash = password_hash($pwd, PASSWORD_DEFAULT);

Lorsqu'un utilisateur se connecte tu dois alors vérifier si son mot de passe enregistré a besoin d'être rehashé, pour s'adapter au nouvel algo ou à la nouvelle dificulté

if (password_verify($input_pwd, $hash)) {
    // login correct
    if (password_needs_rehash($hash, PASSWORD_DEFAULT)) {
            $new_hash = password_hash($input_pwd, PASSWORD_DEFAULT);
            save($new_hash);
    }
}
sha1(sha1(sha1(sha1(sha1(sha1(sha1(sha1(sha1(sha1(sha1("allo ça sert à rien")))))))))));

Il y a pas 10 000 façons de sécuriser en ajoutant un salt. C'est inutile de hasher le mot de passe ou le salt avant le hashage de l'ensemble. En théorie, personne sur terre ne devrait trouver la bonne combinaison avec les ordinateurs actuelles, ça prendrait des années! Il y a clairement des façons plus efficasses de pirater un site, ça ne sera pas en contrant le hashage des mots de passe. :-)

sha1($motDePasse . $salt);