ASP.NET Identity default Mot de passe Hasher, comment ça marche et est-il sécurisé?

Je me demande si le mot de passe hasher implémenté par défaut dans UserManager fourni avec MVC 5 et ASP.NET Identity Framework est suffisamment sécurisé? Et si oui, si vous pouviez m’expliquer comment cela fonctionne?

L’interface IPasswordHasher ressemble à ceci:

public interface IPasswordHasher { ssortingng HashPassword(ssortingng password); PasswordVerificationResult VerifyHashedPassword(ssortingng hashedPassword, ssortingng providedPassword); } 

Comme vous pouvez le voir, cela ne prend pas de sel, mais il est mentionné dans ce fil de discussion: ” Asp.net Identity password hashing ” ( hachage de mot de passe d’identité Asp.net) . Alors je me demande comment ça se passe? Et d’où vient ce sel?

Ce qui me préoccupe, c’est que le sel est statique, ce qui le rend peu sûr.

Voici comment fonctionne l’ implémentation par défaut . Il utilise une fonction de dérivation de clé avec un sel aléatoire pour produire le hachage. Le sel est inclus dans la production de la KDF. Ainsi, chaque fois que vous “hachez” le même mot de passe, vous obtiendrez des hachages différents. Pour vérifier le hachage, la sortie est fractionnée en sel et le rest, et le KDF est exécuté à nouveau sur le mot de passe avec le sel spécifié. Si le résultat correspond au rest de la sortie initiale, le hachage est vérifié.

Hachage:

 public static ssortingng HashPassword(ssortingng password) { byte[] salt; byte[] buffer2; if (password == null) { throw new ArgumentNullException("password"); } using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8)) { salt = bytes.Salt; buffer2 = bytes.GetBytes(0x20); } byte[] dst = new byte[0x31]; Buffer.BlockCopy(salt, 0, dst, 1, 0x10); Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20); return Convert.ToBase64Ssortingng(dst); } 

Vérification:

 public static bool VerifyHashedPassword(ssortingng hashedPassword, ssortingng password) { byte[] buffer4; if (hashedPassword == null) { return false; } if (password == null) { throw new ArgumentNullException("password"); } byte[] src = Convert.FromBase64Ssortingng(hashedPassword); if ((src.Length != 0x31) || (src[0] != 0)) { return false; } byte[] dst = new byte[0x10]; Buffer.BlockCopy(src, 1, dst, 0, 0x10); byte[] buffer3 = new byte[0x20]; Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20); using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8)) { buffer4 = bytes.GetBytes(0x20); } return ByteArraysEqual(buffer3, buffer4); } 

Parce que ces jours-ci ASP.NET est open source, vous pouvez le trouver sur GitHub: AspNet.Identity 3.0 et AspNet.Identity 2.0 .

D’après les commentaires:

 /* ======================= * HASHED PASSWORD FORMATS * ======================= * * Version 2: * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. * (See also: SDL crypto guidelines v5.1, Part III) * Format: { 0x00, salt, subkey } * * Version 3: * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } * (All UInt32s are stored big-endian.) */ 

Je comprends la réponse acceptée et je l’ai voté, mais j’ai pensé que je laisserais la réponse à mes laïcs ici …

Créer un hash

  1. Le sel est généré de manière aléatoire à l’aide de la fonction Rfc2898DeriveBytes qui génère un hachage et un sel. Les entrées de Rfc2898DeriveBytes sont le mot de passe, la taille du sel à générer et le nombre d’itérations de hachage à effectuer. https://msdn.microsoft.com/en-us/library/h83s4e12(v=vs.110).aspx
  2. Le sel et le hachage sont alors mélangés ensemble (sel d’abord suivi du hachage) et codés sous forme de chaîne (le sel est donc codé dans le hachage). Ce hachage codé (qui contient le sel et le hachage) est ensuite stocké (généralement) dans la firebase database par rapport à l’utilisateur.

Vérification d’un mot de passe contre un hachage

Pour vérifier un mot de passe saisi par un utilisateur

  1. Le sel est extrait du mot de passe haché stocké.
  2. Le sel est utilisé pour hacher le mot de passe de l’utilisateur en utilisant une surcharge de Rfc2898DeriveBytes qui prend un sel au lieu d’en générer un. https://msdn.microsoft.com/en-us/library/yx129kfs(v=vs.110).aspx
  3. Le hachage stocké et le hachage de test sont ensuite comparés.

Le Hash

Sous les couvertures, le hachage est généré à l’aide de la fonction de hachage SHA1 ( https://en.wikipedia.org/wiki/SHA-1 ). Cette fonction est appelée itérativement 1000 fois (dans l’implémentation d’identité par défaut)

Pourquoi est-ce sécurisé

  • Les sels aléatoires signifient qu’un attaquant ne peut pas utiliser une table de hachage pré-générée pour essayer de casser les mots de passe. Ils devraient générer une table de hachage pour chaque sel. (En supposant ici que le pirate a également compromis votre sel)
  • Si 2 mots de passe sont identiques, ils auront des hachages différents. (ce qui signifie que les attaquants ne peuvent pas déduire les mots de passe «communs»)
  • Appeler de manière itérative SHA1 1000 fois signifie que l’attaquant doit également le faire. L’idée étant qu’à moins d’avoir du temps sur un superordinateur, ils n’auront pas assez de ressources pour forcer brutalement le mot de passe du hachage. Cela ralentirait massivement le temps nécessaire pour générer une table de hachage pour un sel donné.

Pour ceux qui, comme moi, sont novices dans ce domaine, voici le code avec const et un moyen réel de comparer les octets []. J’ai obtenu tout ce code de stackoverflow mais défini consts donc les valeurs peuvent être modifiées et aussi

 // 24 = 192 bits private const int SaltByteSize = 24; private const int HashByteSize = 24; private const int HasingIterationsCount = 10101; public static ssortingng HashPassword(ssortingng password) { // http://stackoverflow.com/questions/19957176/asp-net-identity-password-hashing byte[] salt; byte[] buffer2; if (password == null) { throw new ArgumentNullException("password"); } using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, SaltByteSize, HasingIterationsCount)) { salt = bytes.Salt; buffer2 = bytes.GetBytes(HashByteSize); } byte[] dst = new byte[(SaltByteSize + HashByteSize) + 1]; Buffer.BlockCopy(salt, 0, dst, 1, SaltByteSize); Buffer.BlockCopy(buffer2, 0, dst, SaltByteSize + 1, HashByteSize); return Convert.ToBase64Ssortingng(dst); } public static bool VerifyHashedPassword(ssortingng hashedPassword, ssortingng password) { byte[] _passwordHashBytes; int _arrayLen = (SaltByteSize + HashByteSize) + 1; if (hashedPassword == null) { return false; } if (password == null) { throw new ArgumentNullException("password"); } byte[] src = Convert.FromBase64Ssortingng(hashedPassword); if ((src.Length != _arrayLen) || (src[0] != 0)) { return false; } byte[] _currentSaltBytes = new byte[SaltByteSize]; Buffer.BlockCopy(src, 1, _currentSaltBytes, 0, SaltByteSize); byte[] _currentHashBytes = new byte[HashByteSize]; Buffer.BlockCopy(src, SaltByteSize + 1, _currentHashBytes, 0, HashByteSize); using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, _currentSaltBytes, HasingIterationsCount)) { _passwordHashBytes = bytes.GetBytes(SaltByteSize); } return AreHashesEqual(_currentHashBytes, _passwordHashBytes); } private static bool AreHashesEqual(byte[] firstHash, byte[] secondHash) { int _minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length; var xor = firstHash.Length ^ secondHash.Length; for (int i = 0; i < _minHashLength; i++) xor |= firstHash[i] ^ secondHash[i]; return 0 == xor; } 

Dans votre ApplicationUserManager personnalisé, vous définissez la propriété PasswordHasher comme le nom de la classe contenant le code ci-dessus.