ASP.NET Identity default Password Hasher, как он работает и является ли он безопасным?

Мне интересно, есть ли пароль Hasher, который по умолчанию реализован в UserManager это поставляется с MVC 5 и ASP.NET Identity Framework, достаточно ли безопасно? И если да, то не могли бы вы объяснить мне, как это работает?

интерфейс IPasswordHasher выглядит следующим образом:

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

Как вы можете видеть, соль не требуется, но она упоминается в этой теме:"Asp.net хэширование пароля идентификации" что он делает infact соль оно за сцены. Поэтому мне интересно, как он это делает? И откуда взялась эта соль?

меня беспокоит то, что соль статична, что делает ее довольно небезопасной.

4 ответов


вот как реализация по умолчанию строительство. Он использует Функция Вывода Ключа со случайной солью для получения хэша. Соль включена как часть выхода KDF. Таким образом, каждый раз, когда вы "хэш" один и тот же пароль вы получите разные хэши. Для проверки хэша вывод разделяется обратно на соль и остальное, и KDF снова запускается по паролю с указанной солью. Если результат соответствует остальной части начального вывода, хэш верифицированный.

хеширования:

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

проверка:

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

потому что в эти дни ASP.NET является открытым исходным кодом, вы можете найти его на GitHub: AspNet.Identity 3.0 и AspNet.Identity 2.0.

из комментариев:

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

Я понимаю принятый ответ и проголосовал за него, но подумал, что я сброшу свой непрофессиональный ответ здесь...

создание хэш -

  1. соль генерируется случайным образом с помощью функции Rfc2898DeriveBytes, которая генерирует хэш и соль. Входы в Rfc2898DeriveBytes - это пароль, размер соли для генерации и количество итераций хэширования для выполнять. https://msdn.microsoft.com/en-us/library/h83s4e12 (v=против 110).aspx
  2. соль и хэш затем пюре вместе(соль сначала следует хэшем) и кодируется как строка (поэтому соль кодируется в гашиш.) Этот закодированный хэш (который содержит соль и хэш) затем хранится (как правило) в базе данных против пользователя.

проверка пароля на хэш -

чтобы проверить пароль, который пользователь входной.

  1. соль извлекается из сохраненного хэшированного пароля.
  2. соль используется для хэширования пароля ввода пользователей, используя перегрузку Rfc2898DeriveBytes который принимает соль вместо генерации. https://msdn.microsoft.com/en-us/library/yx129kfs (v=против 110).aspx
  3. затем сравниваются сохраненный хэш и тестовый хэш.

Хэш

под одеялом хэш генерируется с помощью хэш-функции SHA1 (https://en.wikipedia.org/wiki/SHA-1). Эта функция вызывается итеративно 1000 раз (в реализации идентификатора по умолчанию)

почему это безопасно

  • случайные соли означает, что злоумышленник не может использовать предварительно сгенерированную таблицу из хэшей, чтобы попытаться взломать пароли. Им нужно будет создать хэш-стол для каждой соли. (Предполагая здесь, что хакер также скомпрометировал ваш соль)
  • если 2 пароля совпадают, они будут имеют разные хэши. (это означает, что злоумышленники не могут вывести " общий’ пароли)
  • итеративный вызов SHA1 1000 раз означает, что злоумышленник также должен это сделать. Идея в том, что если у них нет время на суперкомпьютере у них не хватит ресурсов, чтобы грубить. выньте пароль из хэша. Это значительно замедлит время создания хэш-таблицы для данной соли.

для таких, как я, которые являются совершенно новыми для этого, вот код с const и фактический способ сравнить байт []. Я получил весь этот код из stackoverflow, но определил const, чтобы значения могли быть изменены, а также

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

в пользовательском ApplicationUserManager задайте свойству PasswordHasher имя класса, содержащего приведенный выше код.