Comment bcrypt peut-il avoir des sels intégrés?

617

L'article de Coda Hale «Comment stocker un mot de passe en toute sécurité» affirme que:

bcrypt a des sels intégrés pour empêcher les attaques de table arc-en-ciel.

Il cite cet article , qui dit que dans la mise en œuvre d'OpenBSD de bcrypt:

OpenBSD génère le sel bcrypt 128 bits à partir d'un flux de clés arcfour (arc4random (3)), ensemencé avec des données aléatoires que le noyau recueille à partir des synchronisations des périphériques.

Je ne comprends pas comment cela peut fonctionner. Dans ma conception d'un sel:

  • Il doit être différent pour chaque mot de passe stocké, de sorte qu'une table arc-en-ciel distincte devrait être générée pour chaque
  • Il doit être stocké quelque part afin qu'il soit reproductible: lorsqu'un utilisateur essaie de se connecter, nous prenons sa tentative de mot de passe, répétons la même procédure de sel et de hachage que nous avons faite lorsque nous avons initialement stocké son mot de passe et comparons

Lorsque j'utilise Devise (un gestionnaire de connexion Rails) avec bcrypt, il n'y a pas de colonne de sel dans la base de données, donc je suis confus. Si le sel est aléatoire et n'est stocké nulle part, comment pouvons-nous répéter de manière fiable le processus de hachage?

En bref, comment bcrypt peut-il avoir des sels intégrés ?

Nathan Long
la source

Réponses:

789

C'est bcrypt:

Générez un sel aléatoire. Un facteur "coût" a été préconfiguré. Récupérez un mot de passe.

Dérivez une clé de chiffrement du mot de passe en utilisant le sel et le facteur de coût. Utilisez-le pour crypter une chaîne bien connue. Stockez le coût, le sel et le texte chiffré. Parce que ces trois éléments ont une longueur connue, il est facile de les concaténer et de les stocker dans un seul champ, mais de pouvoir les séparer plus tard.

Lorsque quelqu'un essaie de s'authentifier, récupérez le coût et le sel stockés. Dérivez une clé du mot de passe d'entrée, du coût et du sel. Chiffrez la même chaîne bien connue. Si le texte chiffré généré correspond au texte chiffré stocké, le mot de passe est une correspondance.

Bcrypt fonctionne de manière très similaire aux schémas plus traditionnels basés sur des algorithmes comme PBKDF2. La principale différence est son utilisation d'une clé dérivée pour crypter le texte brut connu; d'autres schémas supposent (raisonnablement) que la fonction de dérivation de clé est irréversible et stockent directement la clé dérivée.


Stocké dans la base de données, un bcrypt"hachage" pourrait ressembler à ceci:

$ 2a $ 10 $ vI8aWBnW3fID.ZQ4 / zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa

Il s'agit en fait de trois champs, délimités par "$":

  • 2aidentifie la bcryptversion d'algorithme qui a été utilisée.
  • 10est le facteur de coût; 2 10 itérations de la fonction de dérivation de clé sont utilisées (ce qui n'est pas suffisant, d'ailleurs. Je recommanderais un coût de 12 ou plus.)
  • vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTaest le sel et le texte chiffré, concaténés et codés dans une base 64 modifiée. Les 22 premiers caractères décodent en une valeur de 16 octets pour le sel. Les caractères restants sont du texte chiffré à comparer pour l'authentification.

Cet exemple est tiré de la documentation de l'implémentation ruby ​​de Coda Hale.

erickson
la source
7
Auriez-vous plus de détails sur les raisons pour lesquelles un facteur de coût de 10 ne serait pas suffisant? Dans Grails, j'ai remarqué que 10 est la valeur par défaut pour le facteur de coût / rondes de journal pour bcrypt, donc cela pourrait valoir la peine d'être mis à jour compte tenu de votre suggestion.
pm_labs
57
Le facteur de coût pour bcrypt est exponentiel, ou plutôt, un facteur de coût de 10 signifie 2 ^ 10 tours (1024), un facteur de coût de 16 signifierait 2 ^ 16 tours (65536). Il est alors naturel que cela prenne 5 à 10 secondes. Cela devrait prendre environ 64 fois plus longtemps qu'un facteur de coût de 10. Pour effacer d'autres informations erronées, la fonction crypt de PHP utilise la bibliothèque de crypt Unix qui est implémentée en c.
thomasrutter
3
@TJChambers C'est vrai; si vous pouvez définir le mot de passe sur le compte, vous pourrez vous authentifier. Le hachage de mot de passe n'est pas destiné à empêcher cette attaque. Il est destiné à empêcher un attaquant disposant d'un accès en lecture seule à la table des mots de passe de s'authentifier. Par exemple, vous obtenez une bande de sauvegarde avec la table dessus.
erickson
8
@LobsterMan Non, pas vraiment. Si vous pouviez garder un secret, vous n'utiliseriez pas cette approche, vous stockeriez simplement le mot de passe. Les schémas d'authentification par mot de passe sont basés sur l'hypothèse que l'attaquant a découvert tout ce que vous savez. Le sel est là pour exiger que chaque mot de passe soit attaqué individuellement. L'effort de calcul requis pour tester les mots de passe est régi par les itérations. Si les utilisateurs choisissent de bons mots de passe, ils seront sécurisés, même si le sel est révélé. Cacher le sel pourrait aider quelqu'un avec un mauvais mot de passe dans certains cas, mais je travaillerais d'abord sur la qualité du mot de passe.
erickson
1
@NLV C'est une chaîne définie dans la spécification bcrypt:"OrpheanBeholderScryDoubt"
erickson
182

Je pense que cette phrase aurait dû être formulée comme suit:

bcrypt a des sels intégrés dans les hachages générés pour empêcher les attaques de table arc-en-ciel.

L' bcryptutilitaire lui-même ne semble pas tenir une liste de sels. Au contraire, les sels sont générés de manière aléatoire et ajoutés à la sortie de la fonction afin qu'ils soient mémorisés plus tard (selon l'implémentation Java debcrypt ). Autrement dit, le «hachage» généré par bcryptn'est pas seulement le hachage. Il s'agit plutôt du hachage et du sel concaténés.

Adam Paynter
la source
20
OK, je m'inscris donc sur un site et choisis le mot de passe "foo". Bcryptajoute un sel aléatoire de "akd2! *", résultant en "fooakd2! *", qui est haché et stocké. Plus tard, j'essaye de me connecter avec le mot de passe "bar". Pour voir si j'ai raison, il faut hacher "barakd2! *". Si le sel a été généré de manière aléatoire pour commencer, comment sait-il comment l'ajouter à nouveau à "bar" avant de le hacher et de le comparer?
Nathan Long
46
@Nathan: bcryptsait comment extraire le sel de la sortie générée (qui est stockée dans la base de données). Quand vient le temps de s'authentifier, bcryptsépare la sortie d'origine en ses composants de hachage et de sel. Le composant salt est appliqué au mot de passe entrant saisi par l'utilisateur.
Adam Paynter
22
Pour répondre au commentaire de Nathan Long, une bonne façon de penser est que les sels ne sont pas censés être secrets. C'est pourquoi le sel est inclus dans la sortie de la fonction bcrypt comme l'une des réponses indiquées ci-dessus. Le sel est là pour empêcher les tables arc-en-ciel, qui sont des listes de mots de passe courants, ou simplement de la force brute, etc ... de mots de passe différents mais hachés. Sans sel, le hachage pour un mot de passe dans la base de données A serait identique à un hachage pour un mot de passe dans la base de données B.Le sel modifie simplement les valeurs de hachage, ce qui rend plus difficile pour quelqu'un qui a volé la base de données de décrypter (hacher) les mots de passe.
Joseph Astrahan
11
@Nathan, mais un attaquant peut-il simplement supprimer les sels connus de tous les mots de passe, puis créer une table avec eux?
Oscar
3
Voici comment je le comprends: L'idée est que chaque mot de passe a un sel unique. Le sel est incorporé dans le hachage de mot de passe, de sorte qu'un pirate devrait créer une table arc-en-ciel pour chaque mot de passe. Cela prendrait énormément de temps pour une base de données modérée. Il s'agit de ralentir un attaquant et donc de rendre le forçage brut inutile.
PVermeer
0

Pour rendre les choses encore plus claires,

Inscription / Direction de connexion ->

Le mot de passe + sel est crypté avec une clé générée à partir de: cost, salt et le mot de passe. nous appelons cette valeur chiffrée le cipher text. puis nous attachons le sel à cette valeur et l'encodons en utilisant base64. y attacher le coût et c'est la chaîne produite à partir de bcrypt:

$2a$COST$BASE64

Cette valeur est éventuellement stockée.

Que devrait faire l'attaquant pour trouver le mot de passe? (autre direction <-)

Dans le cas où l'attaquant aurait pris le contrôle de la base de données, l'attaquant décodera facilement la valeur base64, puis il pourra voir le sel. le sel n'est pas secret. bien qu'il soit aléatoire. Ensuite, il devra déchiffrer lecipher text .

Ce qui est plus important: il n'y a pas de hachage dans ce processus, mais un chiffrement - déchiffrement coûteux du processeur. les tables arc-en-ciel sont donc moins pertinentes ici.

jony89
la source
-2

Il s'agit de la documentation de l'interface PasswordEncoder de Spring Security,

 * @param rawPassword the raw password to encode and match
 * @param encodedPassword the encoded password from storage to compare with
 * @return true if the raw password, after encoding, matches the encoded password from
 * storage
 */
boolean matches(CharSequence rawPassword, String encodedPassword);

Ce qui signifie, il faudra faire correspondre rawPassword que l'utilisateur entrera à nouveau lors de la prochaine connexion et le faire correspondre avec un mot de passe codé Bcrypt qui est stocké dans la base de données lors de la connexion / inscription précédente.

Rencontrez Shah
la source
Cela ne répond pas du tout à la question ... Cela ne dit rien sur la façon dont bcrypt peut avoir des sels
intégrés