Хеширование паролей с помощью MD5 или sha-256 C #

Я пишу регистрационную форму для приложения, но все еще испытываю проблемы с новичком в C #.

Я хочу зашифровать / хэшировать пароли для md5 или sha-256, предпочтительно sha-256.

Есть хорошие примеры? Я хочу, чтобы он мог получать информацию из "строки пароля;" а затем хешировать его и сохранить в переменной "string hPassword;". Любые идеи?


person Sean    schedule 01.12.2010    source источник
comment
Что вы собираетесь делать с хешированным паролем? Хранить в базе данных? Тогда простого хеширования недостаточно (Rainbow table). Используйте соли.   -  person dtb    schedule 02.12.2010
comment
Я буду хранить его в базе данных. Что вы порекомендуете.   -  person Sean    schedule 02.12.2010
comment
Люди, пожалуйста, знайте, что ответы ниже уже устарели. И имейте в виду, что вам никогда больше не следует использовать MD5 для хеширования пароля. Он старый, сломанный и устаревший. Самым простым, современным и безопасным решением для большинства людей будет bcrypt. По крайней мере, когда был написан этот комментарий :) Argon2, вероятно, скоро станет стандартной рекомендацией.   -  person Sammi    schedule 07.04.2016


Ответы (9)


Не используйте простой хеш или даже соленый хеш. Используйте какой-нибудь метод усиления ключей, например bcryptРеализация .NET здесь) или PBKDF2встроенная реализация).

Вот пример использования PBKDF2.

Чтобы сгенерировать ключ из вашего пароля ...

string password = GetPasswordFromUserInput();

// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
    byte[] salt = deriveBytes.Salt;
    byte[] key = deriveBytes.GetBytes(20);  // derive a 20-byte key

    // save salt and key to database
}

А затем проверить, действителен ли пароль ...

string password = GetPasswordFromUserInput();

byte[] salt, key;
// load salt and key from database

using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
    byte[] newKey = deriveBytes.GetBytes(20);  // derive a 20-byte key

    if (!newKey.SequenceEqual(key))
        throw new InvalidOperationException("Password is invalid!");
}
person LukeH    schedule 02.12.2010
comment
Я предполагаю, что в вашем сообщении, где вы говорите из PBKDF2, вы намеревались сказать ИЛИ. Я бы не рекомендовал использовать bcrypt в .NET, StackOverflow отказался от bcrypt для использования PBKDF2. См. Примечания Кевина Монтроуза в блоге StackOverflow blog.stackoverflow.com/2011 / 05 / - person Chris Marisic; 08.08.2011
comment
@Chris: Да, я имел в виду PBKDF2; обновлено. Лично я бы тоже выбрал PBKDF2, хотя я не знаю каких-либо веских причин не использовать bcrypt. (Я слышал только хорошее об этом.) - person LukeH; 08.08.2011
comment
Проблема с bcrypt заключается в том, что его реализация в .NET не была проверена, поэтому StackOverflow решил отказаться от использования bcrypt. Они не хотели тратить ресурсы на проверку реализации, тогда как включенная собственная реализация PBKDF2 в .NET уже была проверена для Microsoft. - person Chris Marisic; 08.08.2011
comment
Единственное, что я хотел бы добавить, это изменить длину соли, которую вы указали, в вики-ссылке говорится, что стандарт рекомендует длину соли не менее 64 бит. - person Jon; 24.11.2012
comment
@Jon: 20 байтов - это 160 бит. - person LukeH; 24.11.2012
comment
да ... мой плохой ... должен был прочитать это более внимательно ... использовал ваш фрагмент кода в моем текущем приложении, очень благодарен. - person Jon; 25.11.2012
comment
Прямая ссылка на то, где детали StackOverflow меняются для использования PBKDF2, находится здесь: blog.stackoverflow.com/2011/05/ - person Brad Parks; 09.08.2013
comment
@LukeH, Какой тип данных использовать при хранении зашифрованного PBKDF2 пароли? кажется, nvarchar(max) имеет проблемы с производительностью. Как вы думаете, можно использовать 128 length изменится ли длина? - person Shaiju T; 13.01.2016
comment
@NH. - Вы правы, эта ссылка больше не подходит ... Я использовал Wayback Machine, чтобы найти старую версию этой страницы, и оказалось, что она была в комментариях к странице, которые с тех пор пропали. они переместили домен в домен .blog. Итак, вот ссылка на то, где данные StackOverflow меняются на использование PBKDF2. - person Brad Parks; 20.10.2017

Вы захотите использовать пространство имен System.Security.Cryptography ; в частности, MD5 class или _ 3_ class.

Немного порисовывая код на этой странице, и зная, что оба класса имеют один и тот же базовый класс (HashAlgorithm), вы можете использовать такую ​​функцию:

public string ComputeHash(string input, HashAlgorithm algorithm)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);

   return BitConverter.ToString(hashedBytes);
}

Тогда вы могли бы назвать это так (для MD5):

string hPassword = ComputeHash(password, new MD5CryptoServiceProvider());

Или для SHA256:

string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider());

Изменить: добавление поддержки соли
Как dtb указал в комментариях, этот код был бы сильнее, если бы он включал возможность добавления соль. Если вы не знакомы с этим, соль - это набор случайных битов, которые включаются в качестве входных данных в функцию хеширования, которая имеет большое значение для предотвращения словарных атак на хешированный пароль (например, с использованием радужная таблица). Вот модифицированная версия функции ComputeHash, которая поддерживает соль:

public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   // Combine salt and input bytes
   Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
   salt.CopyTo(saltedInput, 0);
   inputBytes.CopyTo(saltedInput, salt.Length);

   Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);

   return BitConverter.ToString(hashedBytes);
}

Надеюсь, это было полезно!

person Donut    schedule 01.12.2010
comment
OP имеет строку и хочет, чтобы хеш этой строки был строкой. Классы криптографии имеют дело только с байтовыми массивами. - person dtb; 02.12.2010
comment
ASCIIEncoding.Default неправильно возвращает кодировку UTF-16. Я предлагаю Encoding.UTF8 (ASCII был бы слишком ограничен). - person dtb; 02.12.2010
comment
Если вы теперь также добавите параметр byte[] salt в свой метод ComputeHash и добавите эти байты к inputBytes, тогда мне официально понравится ваш ответ :-) - person dtb; 02.12.2010
comment
@dtb Готово! Спасибо за подсказки :) - person Donut; 02.12.2010
comment
Использование PBKDF2 (через Rfc2898DeriveBytes) более безопасно и фактически требует меньше кода. - person LukeH; 02.12.2010
comment
MD5CryptoServiceProvider и SHA256CryptoServiceProvider (и связанные с ними HashAlgorithms) реализуют интерфейс IDisposable, поэтому их создание и время жизни должны быть оптимально заключены в блок using, а не в новый объект, просто передаваемый в качестве параметра. - person Jesse C. Slicer; 12.05.2012
comment
@Sammi - StackOverflow отказался от Bcrypt в пользу PBKDF2 - person Danny Watson; 03.01.2017

Перед хешированием при сохранении в базе данных пароли всегда следует солить.

Рекомендуемые столбцы базы данных:

  • PasswordSalt: int
  • PasswordHash: двоичный (20)

В большинстве сообщений, которые вы найдете в Интернете, говорится о кодировке ASCII соли и хэша, но в этом нет необходимости и только добавляются ненужные вычисления. Кроме того, если вы используете SHA-1, то на выходе будет всего 20 байтов, поэтому ваше хэш-поле в базе данных должно быть только 20 байтов в длину. Я понимаю ваш вопрос о SHA-256, но, если у вас нет веской причины, использование SHA-1 с солидным значением будет достаточным в большинстве бизнес-практик. Если вы настаиваете на SHA-256, тогда длина хэш-поля в базе данных должна составлять 32 байта.

Ниже приведены несколько функций, которые будут генерировать соль, вычислять хэш и проверять хэш на соответствие паролю.

Приведенная ниже функция соли генерирует криптостойкую соль в виде целого числа из 4 криптографически созданных случайных байтов.

private int GenerateSaltForPassword()
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] saltBytes = new byte[4];
    rng.GetNonZeroBytes(saltBytes);
    return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]);
}

Затем пароль можно хешировать, используя соль с функцией ниже. Соль присоединяется к паролю, а затем вычисляется хэш.


private byte[] ComputePasswordHash(string password, int salt)
{
    byte[] saltBytes = new byte[4];
    saltBytes[0] = (byte)(salt >> 24);
    saltBytes[1] = (byte)(salt >> 16);
    saltBytes[2] = (byte)(salt >> 8);
    saltBytes[3] = (byte)(salt);

    byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);

    byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length];
    System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length);
    System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length);

    SHA1 sha1 = SHA1.Create();
    return sha1.ComputeHash(preHashed);
}

Проверить пароль можно, просто вычислив хэш и затем сравнив его с ожидаемым хешем.


private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash)
{
    byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt);

    return hashedPassword.SequenceEqual(correctPasswordHash);
}

person Adam Spicer    schedule 02.12.2010
comment
-1 для простого SHA-1. Используйте медленный хеш, например PBKDF2, bcrypt или scrypt. Также рекомендуется использовать 64-битную соль, но это небольшая проблема. - person CodesInChaos; 12.05.2012
comment
SHA1 не на 100% надежен, хотя команде Google понадобилось 2 года, чтобы доказать это: security.googleblog.com/2017/02/ Может также использовать SHA-256 или SHA3. - person Daniel Williams; 30.03.2018

TL; DR используйте Microsoft. AspNetCore.Cryptography.KeyDerivation, реализующий PBKDF2 с SHA-512.

Хорошая идея для начала работы с хешированием паролей - это посмотреть, что говорится в рекомендациях OWASP. В список рекомендуемых алгоритмов входят Argon2, PBKDF2, scrypt и bcrypt. Все эти алгоритмы можно настроить так, чтобы регулировать время, необходимое для хеширования пароля, и, соответственно, время его взлома с помощью перебора. Все эти алгоритмы используют соль для защиты от атак радужных таблиц.

Ни один из этих алгоритмов не является ужасно слабым, но есть некоторые отличия:

  • bcrypt существует уже почти 20 лет, широко используется и выдержал испытание временем. Он довольно устойчив к атакам на GPU, но не к FPGA.
  • Argon2 - новейшее дополнение, победившее в конкурсе по хешированию паролей в 2015 году. У него лучшая защита от атак на GPU и FPGA, но, на мой взгляд, он появился слишком недавно.
  • Я не очень разбираюсь в scrypt. Он был разработан для предотвращения атак с ускорением на GPU и FPGA, но я слышал, что он оказался не таким сильным, как первоначально утверждалось.
  • PBKDF2 - это семейство алгоритмов, параметризованных различными хеш-функциями. Он не предлагает специальной защиты от атак на GPU или ASIC, особенно если используется более слабая хеш-функция, такая как SHA-1, но, тем не менее, он сертифицирован FIPS, если это важно для вас, и все еще приемлем, если количество итераций достаточно большой.

Основываясь только на алгоритмах, я бы, вероятно, выбрал bcrypt, PBKDF2 был наименее благоприятным.

Однако это еще не все, потому что даже лучший алгоритм может стать небезопасным из-за плохой реализации. Давайте посмотрим, что доступно для платформы .NET:

  • Bcrypt доступен через bcrypt.net. Говорят, реализация основана на Java jBCrypt. В настоящее время на github 6 участников и 8 проблем (все закрыты). В целом, это выглядит хорошо, однако я не знаю, проводил ли кто-нибудь аудит кода, и трудно сказать, будет ли обновленная версия доступна достаточно скоро, если будет обнаружена уязвимость. Я слышал, что Stack Overflow отказался от использования bcrypt по этим причинам.
  • Вероятно, лучший способ использовать Argon2 - это привязать к известной библиотеке libsodium, например https://github.com/adamcaudill/libsodium-net. Идея состоит в том, что большая часть криптографии реализуется через libsodium, которая имеет значительную поддержку, а «непроверенные» части довольно ограничены. Однако в криптографии детали много значат, поэтому в сочетании с относительно недавним появлением Argon2 я бы рассматривал его как экспериментальный вариант.
  • Долгое время .NET имела встроенную реализацию PBKDF2 через Rfc2898DeriveBytes. Однако реализация может использовать только хеш-функцию SHA-1, которая в настоящее время считается слишком быстрой, чтобы быть безопасной.
  • Наконец, самым последним решением является Microsoft.AspNetCore.Cryptography.KeyDerivation пакет доступен через NuGet. Он предоставляет алгоритм PBKDF2 с хэш-функциями SHA-1, SHA-256 или SHA-512, что значительно лучше, чем Rfc2898DeriveBytes. Самым большим преимуществом здесь является то, что реализация предоставлена ​​Microsoft, и хотя я не могу должным образом оценить криптографическое усердие разработчиков Microsoft по сравнению с разработчиками BCrypt.net или libsodium, просто имеет смысл доверять ему, потому что, если вы запускаете приложение .NET, вы уже сильно полагаются на Microsoft. Мы также можем ожидать, что Microsoft выпустит обновления, если будут обнаружены проблемы с безопасностью. С надеждой.

Подводя итог исследованиям до этого момента, хотя PBKDF2 может быть наименее предпочтительным алгоритмом из четырех, доступность реализации, поставляемой Microsoft, превосходит это, поэтому разумным решением было бы использовать Microsoft.AspNetCore.Cryptography.KeyDerivation.

Последний пакет на данный момент нацелен на .NET Standard 2.0, поэтому доступен в .NET Core 2.0 или .NET Framework 4.6.1 или более поздних версиях. Если вы используете более раннюю версию платформы, можно использовать предыдущую версию пакета, 1.1.3, предназначенный для .NET Framework 4.5.1 или .NET Core 1.0. К сожалению, его невозможно использовать даже в более ранних версиях .NET.

Документация и рабочий пример доступны по адресу docs.microsoft.com. Однако не копируйте и вставляйте его как есть, разработчику еще предстоит принять решения.

Первое решение - какую хеш-функцию использовать. Доступные варианты: SHA-1, SHA-256 и SHA-512. Из них SHA-1 определенно слишком быстр, чтобы быть безопасным, SHA-256 неплох, но я бы рекомендовал SHA-512, потому что, предположительно, его использование в 64-битных операциях затрудняет извлечение выгоды от атак на основе графического процессора.

Затем вам нужно выбрать длину вывода хэша пароля и длину соли. Нет смысла иметь вывод длиннее, чем вывод хэш-функции (например, 512 бит для SHA-512), и, вероятно, было бы наиболее безопасно иметь его именно так. По длине соли мнения расходятся. 128 бит должно быть достаточно, но в любом случае длина, превышающая длину выходного хэша, определенно не дает никаких преимуществ.

Далее идет счет итераций. Чем он больше, тем сложнее взломать хэши паролей, но тем больше времени требуется для входа пользователей в систему. Я бы посоветовал выбрать его так, чтобы хеширование занимало 0,25 - 1 секунду в типичной производственной системе, и в любом случае оно не должно быть меньше 10000.

Обычно вы получаете массив байтов в виде значений соли и хэша. Используйте Base64 для преобразования их в строки. Вы можете использовать два разных столбца в базе данных или объединить соль и пароль в один столбец, используя разделитель, которого нет в Base64.

Не забудьте разработать хранилище хеширования паролей таким образом, чтобы в будущем можно было плавно перейти к лучшему алгоритму хеширования.

person ovolko    schedule 26.01.2018

Если вы собираетесь хранить хешированные пароли, используйте bcrypt вместо SHA-256. Проблема в том, что SHA-256 оптимизирован для скорости, что упрощает атаку грубой силы на пароли, если кто-то получит доступ к вашей базе данных.

Прочтите эту статью: Достаточно радужных таблиц: что нужно знать о схемах безопасных паролей и ответ на предыдущий вопрос SO.

Некоторые цитаты из статьи:

Проблема в том, что MD5 работает быстро. Таковы его современные конкуренты, такие как SHA1 и SHA256. Скорость - это цель разработки современного безопасного хэша, потому что хэши являются строительным блоком почти каждой криптосистемы и обычно выполняются по запросу для каждого пакета или сообщения.

Скорость - это именно то, чего вам не нужно в функции хеширования пароля.


Наконец, мы узнали, что для безопасного хранения паролей у нас есть три разумных варианта: схема MD5 в PHK, схема Bcrypt Провоса-Мазьера и SRP. Мы узнали, что правильный выбор - это Bcrypt.

person Jeff Ogata    schedule 01.12.2010
comment
Что ж, пароли будут храниться в базе данных после хеширования. Совсем не хранится локально. - person Sean; 02.12.2010
comment
Хорошо, тогда каждый пароль должен быть хеширован с уникальной солью, и вы должны использовать bcrypt или что-то в этом роде. Хеширование с использованием соли останавливает атаки с использованием радужной таблицы, а использование более медленного алгоритма увеличивает усилия для атаки грубой силы на хешированные пароли. - person Jeff Ogata; 02.12.2010
comment
Я бы не рекомендовал использовать bcrypt в .NET, StackOverflow отказался от bcrypt для использования PBKDF2. См. Примечания Кевина Монтроуза в блоге StackOverflow blog.stackoverflow.com/2011 / 05 / - person Chris Marisic; 08.08.2011
comment
@ChrisMarisic Полезно отметить, почему StackOverflow не использует bcrypt. Когда вы говорите StackOverflow отказался от bcrypt, может создаться впечатление, что с bcrypt что-то не так. - person Ian Boyd; 18.08.2012

PBKDF2 использует HMACSHA1 ....... если вы хотите более современную реализацию HMACSHA256 или HMACSHA512 и по-прежнему хотите, чтобы растяжение ключа замедляло алгоритм, я предлагаю этот API: https://sourceforge.net/projects/pwdtknet/

person thashiznets    schedule 04.10.2012

Вот полная реализация класса SecuredPassword, не знающего о постоянстве.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


    public class SecuredPassword
    {
        private const int saltSize = 256;
        private readonly byte[] hash;
        private readonly byte[] salt;

        public byte[] Hash
        {
        get { return hash; }
    }

    public byte[] Salt
    {
        get { return salt; }
    }

    public SecuredPassword(string plainPassword)
    {
        if (string.IsNullOrWhiteSpace(plainPassword))
            return; 

        using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize))
        {
            salt = deriveBytes.Salt;
            hash = deriveBytes.GetBytes(saltSize);
        }
    }

    public SecuredPassword(byte[] hash, byte[] salt)
    {
        this.hash = hash;
        this.salt = salt;
    }

    public bool Verify(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
            return false; 

        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
        {
            byte[] newKey = deriveBytes.GetBytes(saltSize);

            return newKey.SequenceEqual(hash);
        }
    }
}

И тесты:

 public class SecuredPasswordTests
{
    [Test]
    public void IsHashed_AsExpected()
    {
        var securedPassword = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.EqualTo("password"));
        Assert.That(securedPassword.Hash.Length, Is.EqualTo(256));
    }

    [Test]
    public void Generates_Unique_Salt()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Salt, Is.Not.Null);
        Assert.That(securedPassword2.Salt, Is.Not.Null);
        Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt));
    }

    [Test]
    public void Generates_Unique_Hash()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.Null);
        Assert.That(securedPassword2.Hash, Is.Not.Null);
        Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash));
    }

    [Test]
    public void Verify_WhenMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("password");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Verify_WhenDifferent_ReturnsFalse()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("Password");
        Assert.That(result, Is.False);
    }

    [Test]
    public void Verify_WhenRehydrated_AndMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password123");

        var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt);

        var result = rehydrated.Verify("password123");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Constructor_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(null));
    }

    [Test]
    public void Constructor_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(string.Empty));
    }

    [Test]
    public void Verify_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null));
    }

    [Test]
    public void Verify_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty));
    }

    [Test]
    public void Verify_When_Null_Password_ReturnsFalse()
    {
        Assert.That(new SecuredPassword("password").Verify(null), Is.False);
    }
}
person Sam Shiles    schedule 06.12.2013

Класс System.Security.Cryptography.SHA256 должен помочь:

http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx

person James Kovacs    schedule 01.12.2010

Пожалуйста, используйте это, так как у меня были те же проблемы раньше, но я могу решить это с помощью небольшого фрагмента кода

    public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
    {
        Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

        // Combine salt and input bytes
        Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
        salt.CopyTo(saltedInput, 0);
        inputBytes.CopyTo(saltedInput, salt.Length);

        Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);


        StringBuilder hex = new StringBuilder(hashedBytes.Length * 2);
        foreach (byte b in hashedBytes)
            hex.AppendFormat("{0:X2}", b);

        return hex.ToString();

    }
person king zecole    schedule 25.05.2017
comment
Вы должны добавить объяснение и увидеть Как мне задать хороший вопрос? - person ; 26.05.2017