web-dev-qa-db-fra.com

Mot de passe Hasher d'ASP.NET Identity - Comment cela fonctionne-t-il et est-il sécurisé?

Je me demande si le hasher de mots de passe implémenté par défaut dans le serManager 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
{
    string HashPassword(string password);
    PasswordVerificationResult VerifyHashedPassword(string hashedPassword, 
                                                       string providedPassword);
}

Comme vous pouvez le constater, cela ne prend pas un sel, mais il est mentionné dans ce fil de discussion: " hachage du mot de passe d'identité Asp.net " qu'il infact le sel dans les coulisses. Alors je me demande comment il fait ça? Et d'où vient ce sel?

Ce qui me préoccupe, c'est que le sel est statique, ce qui le rend assez instable.

148
André Snede Kock

Voici comment implémentation par défaut . Il utilise un fonction de dérivation de clé avec un sel aléatoire pour produire le hachage. Le sel est inclus dans la sortie 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 divisée en sel et le reste et le fichier KDF est exécuté à nouveau avec le mot de passe avec le sel spécifié. Si le résultat correspond au reste de la sortie initiale, le hachage est vérifié.

Hachage:

public static string HashPassword(string 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.ToBase64String(dst);
}

Vérification:

public static bool VerifyHashedPassword(string hashedPassword, string password)
{
    byte[] buffer4;
    if (hashedPassword == null)
    {
        return false;
    }
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    byte[] src = Convert.FromBase64String(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);
}
205
Andrew Savinykh

Comme ces jours ASP.NET est open source, vous pouvez le trouver sur GitHub: AspNet.Identity 3. et AspNet.Identity 2. .

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.)
 */
39
Knelis

Je comprends la réponse acceptée et je l’ai votée favorablement, mais j’ai pensé jeter la réponse de mon profane ici ...

Création d'un hachage

  1. Le sel est généré aléatoirement à 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 ensuite écrasés (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 base de données contre l'utilisateur.

Vérification d'un mot de passe par rapport à un dièse

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 des utilisateurs 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 hachage

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 de manière itérative 1000 fois (dans l'implémentation d'Identity par défaut)

Pourquoi est-ce sécurisé

  • Sels aléatoires signifie qu'un attaquant ne peut pas utiliser une table de hachages pré-générée pour essayer de casser les mots de passe. Ils auraient besoin de 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. (signifiant que les attaquants ne peuvent pas déduire des mots de passe "communs")
  • Appeler de manière itérative 1000 fois SHA1 signifie que l'attaquant doit également le faire. L’idée étant que s’ils n’ont pas le temps de travailler sur un superordinateur, ils n’auront pas assez de ressources pour forcer brutalement le mot de passe à partir du hachage. Cela ralentirait considérablement le temps nécessaire pour générer une table de hachage pour un sel donné.
30
Nattrass

Pour ceux qui comme moi sont nouveaux dans ce domaine, voici le code avec const et un moyen réel de comparer les octets []. Je suis tout ce code de stackoverflow mais consts définis afin que les valeurs puissent être changées et aussi

// 24 = 192 bits
    private const int SaltByteSize = 24;
    private const int HashByteSize = 24;
    private const int HasingIterationsCount = 10101;


    public static string HashPassword(string 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.ToBase64String(dst);
    }

    public static bool VerifyHashedPassword(string hashedPassword, string password)
    {
        byte[] _passwordHashBytes;

        int _arrayLen = (SaltByteSize + HashByteSize) + 1;

        if (hashedPassword == null)
        {
            return false;
        }

        if (password == null)
        {
            throw new ArgumentNullException("password");
        }

        byte[] src = Convert.FromBase64String(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 avec le nom de la classe contenant le code ci-dessus.

8
kfrosty