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

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

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

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

Не используйте простой hash или даже соленый хеш. Используйте какой-то метод усиления ключей, такой как 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!"); } 

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

Немного рисуя код на этой странице , и, зная, что оба classа имеют один и тот же базовый 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 в комментариях, этот код был бы сильнее, если бы он включал способность добавлять соль . Если вы не знакомы с этим, соль представляет собой набор случайных битов, которые includeся в качестве входных данных для функции hashирования, что значительно сокращает атаки словаря на хешированный пароль (например, с использованием таблицы радуги ). Вот модифицированная версия функции 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); } 

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

Вы всегда должны солить пароль перед hashированием при их хранении в базе данных.

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

  • PasswordSalt: int
  • Пароль: двоичный (20)

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

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

Функция соли ниже генерирует криптографически сильную соль как целое из 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); } 

Проверка пароля может быть выполнена просто путем вычисления хеша, а затем сравнения с ожидаемым hashем.

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

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

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

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

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

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


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

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

Вот полная реализация упорства, не знающего class 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); } } 

Класс System.Security.Cryptography.SHA256 должен сделать трюк:

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

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

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

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

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

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

  • bcrypt существует уже почти 20 лет, широко используется и выдержал испытание временем. Он довольно устойчив к атакам GPU, но не к FPGA
  • Аргон2 является новейшим дополнением, являющимся победителем в 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 через class Rfc2898DeriveBytes . Однако реализация может использовать только hash-функцию SHA-1, которая считается слишком быстрой для обеспечения безопасности в настоящее время
  • Наконец, самым последним решением является пакет Microsoft.AspNetCore.Cryptography.KeyDerivation , ansible через NuGet. Он обеспечивает алгоритм PBKDF2 с использованием hash-функций 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, ansible в .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 . Однако, не копируйте-вставляйте его так, как есть, все равно должны приниматься решения разработчика.

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

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

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

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

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

Давайте будем гением компьютера.