it-swarm.com.de

Standardkennwort-Hasher von ASP.NET Identity - Wie funktioniert es und ist es sicher?

Ich frage mich, ob der standardmäßig im serManager implementierte Password Hasher, der mit MVC 5 und ASP.NET Identity Framework geliefert wird, sicher genug ist? Und wenn ja, könnten Sie mir erklären, wie es funktioniert?

Die IPasswordHasher-Oberfläche sieht folgendermaßen aus:

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

Wie Sie sehen können, braucht es kein Salz, aber es wird in diesem Thread erwähnt: " Asp.net Identity Password Hashing ", dass es es hinter den Kulissen versalzen tut. Also frage ich mich, wie das geht? Und woher kommt dieses Salz?

Ich mache mir Sorgen, dass das Salz statisch ist, was es ziemlich unsicher macht.

148

So funktioniert die Standardimplementierung . Es verwendet eine Schlüsselableitungsfunktion mit zufälligem Salz, um den Hash zu erzeugen. Das Salz ist im Output des KDF enthalten. Jedes Mal, wenn Sie dasselbe Passwort "hashen", erhalten Sie unterschiedliche Hashes. Um den Hash zu überprüfen, wird die Ausgabe wieder in das Salt und den Rest aufgeteilt, und die KDF wird erneut mit dem Kennwort mit dem angegebenen Salt ausgeführt. Wenn das Ergebnis mit dem Rest der ursprünglichen Ausgabe übereinstimmt, wird der Hash überprüft.

Hashing:

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);
}

Überprüfung:

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

Da ASP.NET heutzutage Open Source ist, finden Sie es auf GitHub: AspNet.Identity 3. und AspNet.Identity 2. .

Aus den Kommentaren:

/* =======================
 * 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

Ich verstehe die akzeptierte Antwort und habe sie hochgestuft, dachte aber, ich würde die Antwort meiner Laien hier ablegen ...

Erstellen eines Hash

  1. Das Salz wird zufällig mit der Funktion Rfc2898DeriveBytes erzeugt, die einen Hash und ein Salz erzeugt. Eingaben für Rfc2898DeriveBytes sind das Kennwort, die Größe des zu generierenden Salt und die Anzahl der durchzuführenden Hashing-Iterationen. https://msdn.Microsoft.com/en-us/library/h83s4e12 (v = vs.110) .aspx
  2. Das Salz und der Hasch werden dann zusammengepresst (Salz zuerst gefolgt vom Hasch) und als Zeichenfolge codiert (das Salz wird also im Hasch codiert). Dieser verschlüsselte Hash (der Salt und Hash enthält) wird dann (normalerweise) in der Datenbank für den Benutzer gespeichert.

Überprüfen eines Passworts gegen einen Hash

So überprüfen Sie ein vom Benutzer eingegebenes Kennwort.

  1. Das Salz wird aus dem gespeicherten Hash-Passwort extrahiert.
  2. Das Salt wird verwendet, um das vom Benutzer eingegebene Passwort zu hashen, wobei eine Überladung von Rfc2898DeriveBytes verwendet wird, die ein Salt benötigt, anstatt eines zu generieren. https://msdn.Microsoft.com/en-us/library/yx129kfs (v = vs.110) .aspx
  3. Der gespeicherte Hash und der Test-Hash werden dann verglichen.

Der Hash

Unter dem Deckmantel wird der Hash mit der SHA1-Hash-Funktion generiert ( https://en.wikipedia.org/wiki/SHA-1 ). Diese Funktion wird 1000-mal iterativ aufgerufen (in der Standard-Identitätsimplementierung)

Warum ist das sicher

  • Zufällige Saltes bedeuten, dass ein Angreifer keine vorab generierte Hash-Tabelle verwenden kann, um Passwörter zu knacken. Sie müssten für jedes Salz eine Hash-Tabelle erstellen. (Unter der Annahme, dass der Hacker auch Ihr Salz kompromittiert hat)
  • Wenn zwei Passwörter identisch sind, haben sie unterschiedliche Hashes. (Angreifer können also keine "gemeinsamen" Passwörter ableiten.)
  • 1000-maliges iteratives Aufrufen von SHA1 bedeutet, dass der Angreifer dies ebenfalls tun muss. Die Idee ist, dass sie nicht über genügend Ressourcen verfügen, um das Passwort aus dem Hash zu erzwingen, es sei denn, sie haben Zeit auf einem Supercomputer. Dies würde die Zeit zum Generieren einer Hash-Tabelle für ein bestimmtes Salz massiv verlangsamen.
30
Nattrass

Für diejenigen wie mich, die brandneu sind, ist hier Code mit const und eine tatsächliche Möglichkeit, die Bytes [] zu vergleichen. Ich habe den gesamten Code von stackoverflow erhalten, aber consts definiert, damit Werte geändert werden können und auch

// 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;
    }

In Ihrem benutzerdefinierten ApplicationUserManager legen Sie für die PasswordHasher-Eigenschaft den Namen der Klasse fest, die den obigen Code enthält.

8
kfrosty