Засіб за замовчуванням пароля ASP.NET Identity - Як це працює та чи безпечно він?


162

Мені цікаво , чи достатньо безпечним є хешер паролів, який за замовчуванням реалізований у UserManager, який постачається з MVC 5 та ASP.NET Identity Framework? І якщо так, якщо ви могли б пояснити мені, як це працює?

Інтерфейс IPasswordHasher виглядає так:

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

Як бачите, це не потребує солі, але в цій темі згадується: " Хеширование пароля Asp.net Identity ", що робить це незамінною сіллю за кадром. Тож мені цікаво, як це робити? А звідки береться ця сіль?

Мене хвилює те, що сіль є статичною, що робить її досить небезпечною.


Я не думаю, що це безпосередньо відповідає на ваше запитання, але Брок Аллен писав про деякі ваші занепокоєння тут => brockallen.com/2013/10/20/…, а також написав відкрите джерело управління ідентифікацією користувачів та бібліотекою аутентифікації, яка має різні функції котла, як скидання пароля, хешування тощо тощо. github.com/brockallen/BrockAllen.MembershipReboot
Шива

@Shiva Спасибі, я перегляну бібліотеку та відео на сторінці. Але я не хотів би мати справу з зовнішньою бібліотекою. Не, якщо я можу цього уникнути.
André Snede Kock

2
FYI: еквівалент потокового потоку для безпеки. Тож хоча тут ви часто отримаєте добру / правильну відповідь. Експерти є на security.stackexchange.com, особливо коментар "чи це безпечно", я задав подібне запитання, і глибина та якість відповіді були вражаючими.
phil soady

@philsoady Дякую, це має сенс, звичайно, я вже на кількох інших "підфорумах", якщо я не отримаю відповіді, я можу використати, я перейду до securiry.stackexchange.com. І дякую за пораду!
Андре Сніде Кок

Відповіді:


227

Ось як працює реалізація за замовчуванням ( ASP.NET Framework або ASP.NET Core ). Для отримання хешу використовується функція ключового виведення із випадковою сіллю. Сіль включається як частина виходу 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);
}

7
Отже, якщо я правильно це розумію, HashPasswordфункція повертає обидва в тій же строці? І коли ви перевірите це, він знову розбиває його і знову хеширує вхідний пароль прозорого тексту з сіллю з розщеплення і порівнює його з оригінальним хешем?
Андре Сніде Кок

9
@ AndréSnedeHansen, точно. І я теж рекомендую вам запитати або про безпеку, або про криптографію SE. Частина "чи це безпечно" може бути вирішена краще у відповідних контекстах.
Андрій Савіних

1
@shajeerpuzhakkal, як описано у відповіді вище.
Андрій Савіних

3
@AndrewSavinykh Я знаю, ось чому я запитую - в чому сенс? Щоб код виглядав розумнішим? ;) Причиною для мене, що підраховує речі з використанням десяткових чисел, НЕБОЛО інтуїтивніше (у нас все-таки 10 пальців - принаймні, більшість із нас), тому оголошення кількості чогось із використанням шістнадцяткових цифр здається непотрібною обтурацією коду.
Андрій Кірул

1
@ MihaiAlexandru-Ionut var hashedPassword = HashPassword(password); var result = VerifyHashedPassword(hashedPassword, password);- це те, що потрібно зробити. після цього resultмістить правду.
Андрій Савіних

43

Оскільки в ці дні 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.)
 */

Так, і варто зазначити, що в алгоритмі показано zespri.
Андре Сніде Кок

1
Джерело на GitHub - це Asp.Net.Identity 3.0, який все ще знаходиться в попередньому випуску. Джерело функції хеш 2.0 є на CodePlex
David

1
Найновішу реалізацію можна знайти під github.com/dotnet/aspnetcore/blob/master/src/Identity/… зараз. Вони архівували інше сховище;)
FranzHuber23

32

Я розумію прийняту відповідь, і я її проголосував, але подумав, що я відповістиму на це відповідь мирян ...

Створення хешу

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

Перевірка пароля на хеш

Щоб перевірити пароль, який вводить користувач.

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

Хеш

Під обкладинками генерується хеш за допомогою хеш-функції SHA1 ( https://en.wikipedia.org/wiki/SHA-1 ). Цю функцію ітераційно називають 1000 разів (У реалізації ідентичності за замовчуванням)

Чому це безпечно

  • Випадкові солі означають, що зловмисник не може використовувати попередньо сформовану таблицю хешів, щоб спробувати зламати паролі. Їм потрібно створити хеш-таблицю для кожної солі. (Якщо припустити, що хакер також порушив вашу сіль)
  • Якщо два паролі однакові, вони матимуть різні хеші. (значить, зловмисники не можуть зробити "загальні" паролі)
  • Ітераційний виклик SHA1 1000 разів означає, що зловмиснику також потрібно це зробити. Ідея полягає в тому, що, якщо вони не встигнуть на суперкомп'ютер, у них не вистачить ресурсу, щоб грубо примусити пароль з хеша. Це масово сповільнить час для створення хеш-таблиці для даної солі.

Дякуємо за ваше пояснення. У розділі "Створення хеша 2." Ви згадуєте, що сіль і хеш пюре разом, чи знаєте ви, чи зберігається це в PasswordHash в таблиці AspNetUsers. Сіль зберігається де-небудь для мене?
єдиноріг2

1
@ unicorn2 Якщо ви подивитесь на відповідь Андрія Савіних ... У розділі про хеширование виглядає, що сіль зберігається в перших 16 байтах байтового масиву, який закодується Base64 і записується в базу даних. Ви могли б побачити цю кодовану рядок Base64 у таблиці PasswordHash. Все, що можна сказати про рядок Base64, - це те, що приблизно перша третина його солі. Змістовна сіль - це перші 16 байт декодованої версії Base64 повної рядки, що зберігається в таблиці PasswordHash
Nattrass

@Nattrass, Моє розуміння хешів і солей є досить рудиментарним, але якщо сіль легко дістати з хешированного пароля, який сенс засолювання в першу чергу. Я думав, що сіль призначена для додаткового введення в алгоритм хешування, про який неможливо було легко здогадатися.
NSouth

1
@NSouth Унікальна сіль робить хеш унікальним для заданого пароля. Таким чином, два однакових пароля матимуть різні хеші. Отримавши доступ до свого хешу та солі, все одно не зловмисник запам'ятає ваш пароль. Хеш не є оборотним. Їм все одно доведеться грубо застосовувати всі можливі паролі. Унікальна сіль просто означає, що хакер не може зробити загальні паролі, зробивши аналіз частоти для конкретних хешів, якщо їм вдалося влаштувати всю вашу таблицю користувачів.
Nattrass

8

Для таких, як я, які є абсолютно новими в цьому, ось код з const і фактичний спосіб порівняння байтів []. Я отримав увесь цей код від stackoverflow, але визначено consts, щоб значення можна було змінити

// 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 ім'я класу, який містить вищевказаний код.


Для цього .. _passwordHashBytes = bytes.GetBytes(SaltByteSize); Я думаю, ви це мали на увазі _passwordHashBytes = bytes.GetBytes(HashByteSize);. Не має значення у вашому сценарії, оскільки обидва мають однаковий розмір, але загалом ..
Акшата
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.