Хеширование паролей с помощью MD5 или sha-256 C # - PullRequest
43 голосов
/ 02 декабря 2010

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

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

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

Ответы [ 9 ]

78 голосов
/ 02 декабря 2010

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

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

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

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

5 голосов
/ 02 декабря 2010

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

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

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

2 голосов
/ 27 января 2018

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

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

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

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

Исходя из одних алгоритмов, я бы, вероятно, выбрал bcrypt, PBKDF2 - наименее выгодный.

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

  • Bcrypt доступен через bcrypt.net . Говорят, что реализация основана на Java jBCrypt. В настоящее время на github есть 6 участников и 8 выпусков (все закрыты). В целом, это выглядит хорошо, однако я не знаю, проводил ли кто-нибудь аудит кода, и трудно сказать, будет ли обновленная версия доступна достаточно скоро, если будет обнаружена уязвимость. Я слышал, что переполнение стека отошло от использования 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-битных операций затрудняет получение выгоды от атак на основе GPU.

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

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

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

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

2 голосов
/ 06 декабря 2013

Ниже приведена полная реализация класса сохраненного пароля 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);
    }
}
2 голосов
/ 04 октября 2012

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

2 голосов
/ 02 декабря 2010

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

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

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

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

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


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

1 голос
/ 26 мая 2017

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

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

    }
1 голос
/ 02 декабря 2010

Класс System.Security.Cryptography.SHA256 должен выполнить свою задачу:

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

...