Comment hacher des mots de passe longs (> 72 caractères) avec Blowfish

91

La semaine dernière, j'ai lu beaucoup d'articles sur le hachage de mot de passe et Blowfish semble être (l'un des) meilleur algorithme de hachage du moment - mais ce n'est pas le sujet de cette question!

La limite de 72 caractères

Blowfish ne prend en compte que les 72 premiers caractères du mot de passe saisi:

<?php
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);

$input = substr($password, 0, 72);
var_dump($input);

var_dump(password_verify($input, $hash));
?>

La sortie est:

string(119) "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)

Comme vous pouvez le voir, seuls les 72 premiers caractères comptent. Twitter utilise blowfish aka bcrypt pour stocker leurs mots de passe ( https://shouldichangemypassword.com/twitter-hacked.php ) et devinez quoi: changez votre mot de passe Twitter en un mot de passe long de plus de 72 caractères et vous pouvez vous connecter à votre compte en saisir uniquement les 72 premiers caractères.

Blowfish et poivre

Il y a beaucoup d'opinions différentes sur les mots de passe "peppering". Certaines personnes disent que ce n'est pas nécessaire, car vous devez supposer que la chaîne de poivre secrète est également connue / publiée afin qu'elle n'améliore pas le hachage. J'ai un serveur de base de données séparé, il est donc fort possible que seule la base de données soit divulguée et non le poivre constant.

Dans ce cas (pas de fuite de poivre) vous faites une attaque basée sur un dictionnaire plus difficile (corrigez-moi si ce n'est pas juste). Si votre ficelle de poivre fuit également: pas si mal - vous avez toujours le sel et il est aussi bien protégé qu'un hasch sans poivre.

Donc, je pense que pepping le mot de passe n'est au moins pas un mauvais choix.

Suggestion

Ma suggestion pour obtenir un hachage Blowfish pour un mot de passe de plus de 72 caractères (et poivre) est:

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);

// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);

var_dump(password_verify($input_peppered, $hash));
?>

Ceci est basé sur cette question : password_verifyretour false.

La question

Quelle est la manière la plus sûre? Obtenir d'abord un hachage SHA-256 (qui renvoie 64 caractères) ou ne prendre en compte que les 72 premiers caractères du mot de passe?

Avantages

  • L'utilisateur ne peut pas se connecter en entrant uniquement les 72 premiers caractères
  • Vous pouvez ajouter le poivre sans dépasser la limite de caractères
  • La sortie de hash_hmac aurait probablement plus d'entropie que le mot de passe lui-même
  • Le mot de passe est haché par deux fonctions différentes

Les inconvénients

  • Seuls 64 caractères sont utilisés pour créer le hachage Blowfish


Edit 1: Cette question ne concerne que l'intégration PHP de blowfish / bcrypt. Merci pour les commentaires!

Frederik Kammer
la source
3
Blowfish n'est pas le seul à tronquer le mot de passe, induisant en erreur les gens à penser qu'il est plus sûr qu'il ne l'est en réalité. Voici une histoire intéressante de la limite de 8 caractères.
DOK
2
La troncature de 72 caractères est-elle fondamentale pour l'algorithme Blowfish, ou simplement pour l'implémentation PHP? IIRC Blowfish est également utilisé sur (au moins certains) 'nixes pour crypter les mots de passe des utilisateurs.
Douglas B.Staple
3
Le problème vient de Bcrypt, pas de Blowfish. Je peux reproduire ce problème avec Python et Bcrypt seuls.
Blender
@Blender: Merci pour votre commentaire et votre travail. Je n'ai pas pu trouver différentes fonctions dans php pour blowfish et bcrypt et bien qu'elles soient identiques. Mais cela ne fait-il aucune différence pour moi en php? Je préférerais utiliser la fonction php standard.
Frederik Kammer
1
Voir également le framework de hachage de mot de passe PHP d'Openwall (PHPass). Son portable et durci contre un certain nombre d'attaques courantes sur les mots de passe des utilisateurs. Le gars qui a écrit le framework (SolarDesigner) est le même qui a écrit John The Ripper et siège en tant que juge au concours de hachage de mot de passe . Il sait donc une chose ou deux sur les attaques de mots de passe.
jww

Réponses:

135

Le problème ici est essentiellement un problème d'entropie. Alors commençons à y regarder:

Entropie par caractère

Le nombre de bits d'entropie par octet est:

  • Caractères hexagonaux
    • Bits: 4
    • Valeurs: 16
    • Entropie en 72 caractères: 288 bits
  • Alpha-Numérique
    • Bits: 6
    • Valeurs: 62
    • Entropie en 72 caractères: 432 bits
  • Symboles "communs"
    • Bits: 6,5
    • Valeurs: 94
    • Entropie en 72 caractères: 468 bits
  • Octets complets
    • Bits: 8
    • Valeurs: 255
    • Entropie en 72 caractères: 576 bits

Donc, comment nous agissons dépend du type de personnages que nous attendons.

Le premier problème

Le premier problème avec votre code est que votre étape de hachage "pepper" génère des caractères hexadécimaux (puisque le quatrième paramètre hash_hmac()n'est pas défini).

Par conséquent, en hachant votre poivre, vous coupez efficacement l'entropie maximale disponible pour le mot de passe par un facteur de 2 (de 576 à 288 bits possibles ).

Le deuxième problème

Cependant, sha256ne fournit que des 256bits d'entropie en premier lieu. Vous réduisez donc efficacement 576 bits à 256 bits. Votre étape de hachage * immédiatement *, par définition, perd au moins 50% de l' entropie possible dans le mot de passe.

Vous pouvez partiellement résoudre ce problème en passant à SHA512, où vous ne réduisez l'entropie disponible que d'environ 12%. Mais c'est toujours une différence non négligeable. Ces 12% réduisent le nombre de permutations d'un facteur de 1.8e19. C'est un grand nombre ... Et c'est le facteur qui le réduit de ...

Le problème sous-jacent

Le problème sous-jacent est qu'il existe trois types de mots de passe de plus de 72 caractères. L'impact de ce système de style sur eux sera très différent:

Remarque: à partir de maintenant, je suppose que nous comparons à un système de poivre qui utilise SHA512une sortie brute (non hexadécimale).

  • Mots de passe aléatoires à haute entropie

    Ce sont vos utilisateurs qui utilisent des générateurs de mots de passe qui génèrent quel montant en grandes clés pour les mots de passe. Ils sont aléatoires (générés, non choisis par l'homme) et ont une entropie élevée par caractère. Ces types utilisent des octets élevés (caractères> 127) et certains caractères de contrôle.

    Pour ce groupe, votre fonction de hachage réduira considérablement leur entropie disponible dans bcrypt.

    Permettez-moi de le répéter. Pour les utilisateurs qui utilisent des mots de passe longs et à forte entropie, votre solution réduit considérablement la force de leur mot de passe d'une quantité mesurable. (62 bits d'entropie perdus pour un mot de passe de 72 caractères, et plus pour des mots de passe plus longs)

  • Mots de passe aléatoires à entropie moyenne

    Ce groupe utilise des mots de passe contenant des symboles courants, mais pas d'octets de poids fort ni de caractères de contrôle. Ce sont vos mots de passe à saisir.

    Pour ce groupe, vous allez débloquer légèrement plus d'entropie (ne pas le créer, mais permettre à plus d'entropie de s'insérer dans le mot de passe bcrypt). Quand je dis légèrement, je veux dire légèrement. Le seuil de rentabilité se produit lorsque vous maximisez les 512 bits de SHA512. Par conséquent, le pic est à 78 caractères.

    Permettez-moi de le répéter. Pour cette classe de mots de passe, vous ne pouvez stocker que 6 caractères supplémentaires avant de manquer d'entropie.

  • Mots de passe non aléatoires à faible entropie

    Il s'agit du groupe qui utilise des caractères alphanumériques qui ne sont probablement pas générés aléatoirement. Quelque chose comme une citation biblique ou autre. Ces phrases ont environ 2,3 bits d'entropie par caractère.

    Pour ce groupe, vous pouvez déverrouiller considérablement plus d'entropie (pas le créer, mais permettre à plus de tenir dans l'entrée du mot de passe bcrypt) par hachage. Le seuil de rentabilité est d'environ 223 caractères avant de manquer d'entropie.

    Disons cela encore. Pour cette classe de mots de passe, le pré-hachage augmente nettement la sécurité.

Retour au monde réel

Ces types de calculs d'entropie n'ont pas vraiment beaucoup d'importance dans le monde réel. Ce qui compte, c'est de deviner l'entropie. C'est ce qui affecte directement ce que les attaquants peuvent faire. C'est ce que vous voulez maximiser.

Bien que peu de recherches aient été menées pour deviner l'entropie, je voudrais souligner certains points.

Les chances de deviner au hasard 72 caractères corrects d'affilée sont extrêmement faibles. Vous êtes plus susceptible de gagner à la loterie Powerball 21 fois que d'avoir cette collision ... C'est le nombre dont nous parlons.

Mais nous ne pouvons pas tomber dessus statistiquement. Dans le cas de phrases, la probabilité que les 72 premiers caractères soient identiques est bien plus élevée que pour un mot de passe aléatoire. Mais c'est toujours trivialement bas (vous êtes plus susceptible de gagner à la loterie Powerball 5 fois, sur la base de 2,3 bits par caractère).

Pratiquement

Pratiquement, cela n'a pas vraiment d'importance. Les chances que quelqu'un devine correctement les 72 premiers caractères, là où ces derniers font une différence significative, sont si faibles qu'il ne vaut pas la peine de s'inquiéter. Pourquoi?

Eh bien, disons que vous prenez une phrase. Si la personne parvient à bien comprendre les 72 premiers caractères, elle est soit vraiment chanceuse (peu probable), soit c'est une phrase courante. S'il s'agit d'une phrase courante, la seule variable est la durée de la création.

Prenons un exemple. Prenons une citation de la Bible (simplement parce que c'est une source courante de texte long, pas pour une autre raison):

Tu ne convoiteras pas la maison de ton prochain. Vous ne convoitez pas la femme de votre voisin, ni son serviteur ou sa servante, ni son bœuf ou son âne, ni tout ce qui appartient à votre prochain.

Cela fait 180 caractères. Le 73e caractère est gle deuxième neighbor's. Si vous avez deviné cela, vous ne vous arrêterez probablement pas nei, mais continuez avec le reste du verset (puisque c'est ainsi que le mot de passe est susceptible d'être utilisé). Par conséquent, votre "hachage" n'a pas ajouté grand-chose.

BTW: Je ne préconise ABSOLUMENT PAS l'utilisation d'une citation biblique. En fait, c'est exactement le contraire.

Conclusion

Vous n'allez pas vraiment aider les gens qui utilisent des mots de passe longs en hachant d'abord. Vous pouvez certainement aider certains groupes. Certains peuvent certainement blesser.

Mais en fin de compte, rien de tout cela n'est trop important. Les chiffres que nous traitons sont juste MANIÈRE trop élevé. La différence d'entropie ne sera pas beaucoup.

Vous feriez mieux de laisser bcrypt tel quel. Vous êtes plus susceptible de bousiller le hachage (littéralement, vous l'avez déjà fait, et vous n'êtes pas le premier ou le dernier à faire cette erreur) que l'attaque que vous essayez d'empêcher va se produire.

Concentrez-vous sur la sécurisation du reste du site. Et ajoutez un compteur d'entropie de mot de passe à la boîte de mot de passe lors de l'enregistrement pour indiquer la force du mot de passe (et indiquer si un mot de passe est trop long que l'utilisateur peut souhaiter le changer) ...

C'est mon 0,02 $ au moins (ou peut-être bien plus de 0,02 $) ...

En ce qui concerne l'utilisation d'un poivre «secret»:

Il n'y a littéralement aucune recherche sur l'alimentation d'une fonction de hachage dans bcrypt. Par conséquent, il n'est pas clair, au mieux, si l'introduction d'un hachage «poivré» dans bcrypt causera un jour des vulnérabilités inconnues (nous savons que cela hash1(hash2($value))peut exposer des vulnérabilités importantes autour de la résistance aux collisions et des attaques de pré-image).

Étant donné que vous envisagez déjà de stocker une clé secrète (le «poivre»), pourquoi ne pas l'utiliser d'une manière bien étudiée et comprise? Pourquoi ne pas crypter le hachage avant de le stocker?

Fondamentalement, après avoir haché le mot de passe, insérez l'intégralité de la sortie de hachage dans un algorithme de chiffrement fort. Puis stockez le résultat chiffré.

Désormais, une attaque SQL-Injection ne divulguera rien d'utile, car ils n'ont pas la clé de chiffrement. Et si la clé est divulguée, les attaquants ne sont pas mieux lotis que si vous utilisiez un simple hachage (ce qui est prouvable, quelque chose avec le poivre "pré-hachage" ne fournit pas).

Remarque: si vous choisissez de faire cela, utilisez une bibliothèque. Pour PHP, je recommande fortement le Zend\Cryptpackage de Zend Framework 2 . C'est en fait le seul que je recommande à l'heure actuelle. Il a été fortement revu, et il prend toutes les décisions pour vous (ce qui est une très bonne chose) ...

Quelque chose comme:

use Zend\Crypt\BlockCipher;

public function createHash($password) {
    $hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);

    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    return $blockCipher->encrypt($hash);
}

public function verifyHash($password, $hash) {
    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    $hash = $blockCipher->decrypt($hash);

    return password_verify($password, $hash);
}

Et c'est avantageux parce que vous utilisez tous les algorithmes de manière bien comprise et bien étudiée (au moins relativement). Rappelles toi:

N'importe qui, de l'amateur le plus ignorant au meilleur cryptographe, peut créer un algorithme qu'il ne peut pas casser lui-même.

ircmaxell
la source
6
Merci beaucoup pour cette réponse détaillée. Cela m'aide vraiment!
Frederik Kammer
1
Mon compliment pour cette réponse. Un petit choix cependant, c'est la grande majorité des utilisateurs, qui utilisent des mots de passe, des mots et des dérivés très faibles contenus dans un dictionnaire pour craquer les mots de passe, un poivre les protégerait indépendamment des questions d'entrophie. Pour éviter de perdre l'entrophie, vous pouvez simplement concaténer le mot de passe et le poivre. Cependant, votre suggestion concernant le chiffrement de la valeur de hachage est probablement la meilleure solution pour ajouter un secret côté serveur.
martinstoeckli
2
@martinstoeckli: Mon problème avec le concept de poivre n'est pas dans sa valeur. C'est en ce que l'application du «poivre» entre en territoire inexploré en termes d'algorithmes cryptographiques. Ce n'est pas une bonne chose. Au lieu de cela, je pense que les primitives cryptographiques devraient être combinées de manière à ce qu'elles soient conçues pour aller ensemble. Fondamentalement, le concept de base d'un poivre me semble dans mes oreilles comme si certaines personnes qui ne savaient rien de la cryptographie disaient: «Plus de hashes c'est mieux! Nous avons du sel, le poivre c'est bien aussi!» . Je préfère simplement avoir un
impl
@ircmaxell - Oui, je connais votre point de vue et je suis d'accord, tant que les valeurs de hachage seront cryptées par la suite. Si vous ne faites pas cette étape supplémentaire, une attaque par dictionnaire révélera simplement trop de mots de passe faibles, même avec un bon algorithme de hachage.
martinstoeckli
@martinstoeckli: Je ne suis pas d'accord là-dessus. Le stockage des secrets n'est pas une chose banale à faire. Au lieu de cela, si vous utilisez bcrypt avec un bon coût (12 aujourd'hui), tous les mots de passe sauf la classe la plus faible sont sûrs (le dictionnaire et les mots de passe triviaux sont les plus faibles). Je recommanderais donc plutôt aux gens de se concentrer sur l'éducation de l'utilisateur avec des compteurs de force et de l'amener à utiliser de meilleurs mots de passe en premier lieu ...
ircmaxell
5

Les mots de passe poivrants sont certainement une bonne chose à faire, mais voyons pourquoi.

Nous devons d'abord répondre à la question de savoir quand exactement un poivre aide. Le poivre ne protège que les mots de passe, tant qu'il reste secret, donc si un attaquant a accès au serveur lui-même, il ne sert à rien. Une attaque beaucoup plus simple est l'injection SQL, qui permet un accès en lecture à la base de données (à nos valeurs de hachage), j'ai préparé une démo d'injection SQL pour montrer à quel point cela peut être facile (cliquez sur la flèche suivante pour obtenir un contribution).

Alors qu'est-ce que le poivre aide réellement? Tant que le poivre reste secret, il protège les mots de passe faibles d'une attaque par dictionnaire. Le mot de passe 1234deviendrait alors quelque chose comme 1234-p*deDIUZeRweretWy+.O. Ce mot de passe n'est pas seulement beaucoup plus long, il contient également des caractères spéciaux et ne fera jamais partie d'aucun dictionnaire.

Maintenant, nous pouvons estimer les mots de passe que nos utilisateurs utiliseront, probablement plus d'utilisateurs entreront des mots de passe faibles, car il y a des utilisateurs avec des mots de passe entre 64 et 72 caractères (en fait, ce sera très rare).

Un autre point est la portée du forçage brutal. La fonction de hachage sha256 renverra une sortie de 256 bits ou des combinaisons 1.2E77, c'est beaucoup trop pour le forçage brutal, même pour les GPU (si je calculais correctement, cela prendrait environ 2E61 ans sur un GPU en 2013). On n'obtient donc pas de réel inconvénient en appliquant le poivre. Parce que les valeurs de hachage ne sont pas systématiques, vous ne pouvez pas accélérer le forçage brutal avec des modèles communs.

PS Pour autant que je sache, la limite de 72 caractères est spécifique à l'algorithme de BCrypt lui-même. La meilleure réponse que j'ai trouvée est la suivante .

PPS Je pense que votre exemple est imparfait, vous ne pouvez pas générer le hachage avec la longueur totale du mot de passe et le vérifier avec un tronçon. Vous vouliez probablement appliquer le poivre de la même manière pour générer le hachage et pour vérifier le hachage.

martinstoeckli
la source
En ce qui concerne votre PPS, je peux simplement dire: Oui, il peut vérifier le mot de passe tronqué avec le hachage du mot de passe non tronqué et toujours obtenir true. C'est le sujet de cette question. Regardez vous-même: viper-7.com/RLKFnB
Sliq
@Panique - Le problème n'est pas le calcul du hachage BCrypt, c'est le HMAC avant. Pour générer le hachage SHA, l'OP utilise le mot de passe complet et utilise le résultat comme entrée pour BCrypt. Pour la vérification, il tronque le mot de passe avant de calculer le hachage SHA, puis utilise ce résultat complètement différent comme entrée pour BCrypt. Le HMAC accepte les entrées de n'importe quelle longueur.
martinstoeckli
2

Bcrypt utilise un algorithme basé sur l'algorithme coûteux de configuration de clé Blowfish.

La limite de mot de passe de 56 octets recommandée (y compris l'octet de terminaison nul) pour bcrypt se rapporte à la limite de 448 bits de la clé Blowfish. Les octets au-delà de cette limite ne sont pas entièrement mélangés dans le hachage résultant. La limite absolue de 72 octets sur les mots de passe bcrypt est donc moins pertinente, si l'on considère l'effet réel sur le hachage résultant de ces octets.

Si vous pensez que vos utilisateurs choisiraient normalement des mots de passe de plus de 55 octets de longueur, rappelez-vous que vous pouvez toujours augmenter les cycles d'étirement des mots de passe à la place, pour augmenter la sécurité en cas de violation de la table des mots de passe (bien que cela doive être beaucoup par rapport à l'ajout de personnages). Si les droits d'accès des utilisateurs sont si critiques que les utilisateurs auraient normalement besoin d'un mot de passe massivement long, l'expiration du mot de passe devrait également être courte, comme 2 semaines. Cela signifie qu'un mot de passe a beaucoup moins de chances de rester valide tandis qu'un pirate informatique investit ses ressources pour vaincre le facteur de travail impliqué dans le test de chaque mot de passe d'essai pour voir s'il produira un hachage correspondant.

Bien sûr, dans le cas où la table des mots de passe n'est pas violée, nous ne devrions permettre aux pirates, au maximum, que dix tentatives pour deviner le mot de passe de 55 octets d'un utilisateur, avant de verrouiller le compte de l'utilisateur;)

Si vous décidez de pré-hacher un mot de passe de plus de 55 octets, vous devez utiliser SHA-384, car il a la plus grande sortie sans dépasser la limite.

Phil
la source
1
"l'expiration du mot de passe devrait également être courte, comme 2 semaines" de "mots de passe massivement longs", vraiment, pourquoi se soucier même d'enregistrer le mot de passe alors, utilisez simplement la réinitialisation du mot de passe à chaque fois. Sérieusement, ce n'est pas la bonne solution, passez à l'authentification à deux facteurs avec un jeton.
zaph
Merci @zaph. Pouvez-vous me donner un exemple de cela? Ça semble intéressant.
Phil
[Ébauche de la publication spéciale du NIST 800-63B Guide d'authentification numérique] ( pages.nist.gov/800-63-3/sp800-63b.html ), 5.1.1.2. Vérificateurs de secrets mémorisés: Les vérificateurs NE DEVRAIENT PAS exiger que les secrets mémorisés soient modifiés arbitrairement (par exemple, périodiquement) . Voir également Toward Better Password Requirements par Jim Fenton.
zaph
1
Le fait est que plus un utilisateur doit souvent changer un mot de passe, plus les choix de mot de passe deviennent pires, réduisant ainsi la sécurité. L'utilisateur a une quantité limitée de bons mots de passe mémorisables et ils s'épuisent, soit en choisissant de très mauvais mots de passe, soit en les écrivant sur des post-it collés au bas du clavier, etc.
zaph