Хэш и соляные пароли в С#

Я просто просматривал одну из статей DavidHayden на Hashing User Passwords.

На самом деле я не могу получить то, что он пытается достичь.

Вот его код:

private static string CreateSalt(int size)
{
    //Generate a cryptographic random number.
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] buff = new byte[size];
    rng.GetBytes(buff);

    // Return a Base64 string representation of the random number.
    return Convert.ToBase64String(buff);
}

private static string CreatePasswordHash(string pwd, string salt)
{
    string saltAndPwd = String.Concat(pwd, salt);
    string hashedPwd =
        FormsAuthentication.HashPasswordForStoringInConfigFile(
        saltAndPwd, "sha1");
    return hashedPwd;
}

Есть ли какой-либо другой метод С# для хеширования паролей и добавления соли к нему?

Ответ 1

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

В моей книге Начиная с ASP.NET Security (о, наконец, оправдание сутенеру книги) Я делаю следующее

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
  HashAlgorithm algorithm = new SHA256Managed();

  byte[] plainTextWithSaltBytes = 
    new byte[plainText.Length + salt.Length];

  for (int i = 0; i < plainText.Length; i++)
  {
    plainTextWithSaltBytes[i] = plainText[i];
  }
  for (int i = 0; i < salt.Length; i++)
  {
    plainTextWithSaltBytes[plainText.Length + i] = salt[i];
  }

  return algorithm.ComputeHash(plainTextWithSaltBytes);            
}

В качестве примера в этом вопросе выступает солеобразование. Вы можете преобразовать текст в байтовые массивы с помощью Encoding.UTF8.GetBytes(string). Если вы должны преобразовать хэш в его строковое представление, вы можете использовать Convert.ToBase64String и Convert.FromBase64String для его преобразования.

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

public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
  if (array1.Length != array2.Length)
  {
    return false;
  }

  for (int i = 0; i < array1.Length; i++)
  {
    if (array1[i] != array2[i])
    {
      return false;
    }
  }

  return true;
}

Всегда используйте новую соль для каждого пароля. Соли не должны храниться в секрете и могут храниться рядом с самим хешем.

Ответ 2

Что сказал удар, но с меньшим количеством кода. Используйте Linq или CopyTo для объединения массивов.

public static byte[] Hash(string value, byte[] salt)
{
    return Hash(Encoding.UTF8.GetBytes(value), salt);
}

public static byte[] Hash(byte[] value, byte[] salt)
{
    byte[] saltedValue = value.Concat(salt).ToArray();
    // Alternatively use CopyTo.
    //var saltedValue = new byte[value.Length + salt.Length];
    //value.CopyTo(saltedValue, 0);
    //salt.CopyTo(saltedValue, value.Length);

    return new SHA256Managed().ComputeHash(saltedValue);
}

У Linq есть простой способ сравнить ваши байтовые массивы.

public bool ConfirmPassword(string password)
{
    byte[] passwordHash = Hash(password, _passwordSalt);

    return _passwordHash.SequenceEqual(passwordHash);
}

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

С этой целью существует Rfc2898DeriveBytes класс, который медленный (и может быть сделан медленнее), и может ответить на вторую часть оригинального вопроса в том, что он может принимать пароль и соль и возвращать хэш. См. этот вопрос для получения дополнительной информации. Примечание. Stack Exchange использует Rfc2898DeriveBytes для хеширования паролей (исходный код здесь).

Ответ 3

Я читал, что такие функции хеширования, как SHA256, не были предназначены для хранения паролей: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

Вместо этого были использованы адаптивные функции вывода ключей, такие как PBKDF2, bcrypt или scrypt. Вот основанный на PBKDF2 файл, который Microsoft написала для PasswordHasher в своей библиотеке Microsoft.AspNet.Identity:

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

public string HashPassword(string password)
{
    var prf = KeyDerivationPrf.HMACSHA256;
    var rng = RandomNumberGenerator.Create();
    const int iterCount = 10000;
    const int saltSize = 128 / 8;
    const int numBytesRequested = 256 / 8;

    // Produce a version 3 (see comment above) text hash.
    var salt = new byte[saltSize];
    rng.GetBytes(salt);
    var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

    var outputBytes = new byte[13 + salt.Length + subkey.Length];
    outputBytes[0] = 0x01; // format marker
    WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    WriteNetworkByteOrder(outputBytes, 5, iterCount);
    WriteNetworkByteOrder(outputBytes, 9, saltSize);
    Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
    return Convert.ToBase64String(outputBytes);
}

public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
    var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

    // Wrong version
    if (decodedHashedPassword[0] != 0x01)
        return false;

    // Read header information
    var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
    var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
    var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);

    // Read the salt: must be >= 128 bits
    if (saltLength < 128 / 8)
    {
        return false;
    }
    var salt = new byte[saltLength];
    Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);

    // Read the subkey (the rest of the payload): must be >= 128 bits
    var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
    if (subkeyLength < 128 / 8)
    {
        return false;
    }
    var expectedSubkey = new byte[subkeyLength];
    Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);

    // Hash the incoming password and verify it
    var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
    return actualSubkey.SequenceEqual(expectedSubkey);
}

private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
    buffer[offset + 0] = (byte)(value >> 24);
    buffer[offset + 1] = (byte)(value >> 16);
    buffer[offset + 2] = (byte)(value >> 8);
    buffer[offset + 3] = (byte)(value >> 0);
}

private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
    return ((uint)(buffer[offset + 0]) << 24)
        | ((uint)(buffer[offset + 1]) << 16)
        | ((uint)(buffer[offset + 2]) << 8)
        | ((uint)(buffer[offset + 3]));
}

Обратите внимание, что для этого требуется установленный пакет nuget Microsoft.AspNetCore.Cryptography.KeyDerivation, для которого требуется .NET Standard 2.0 (.NET 4.6.1 или выше). Для более ранних версий .NET см. Класс Crypto из библиотеки Microsoft System.Web.Helpers.

Обновление ноябрь 2015
Обновленный ответ для использования реализации из другой библиотеки Microsoft, которая использует хеширование PBKDF2-HMAC-SHA256 вместо PBKDF2-HMAC-SHA1 (обратите внимание, что PBKDF2-HMAC-SHA1 по- прежнему безопасен, если iterCount достаточно высок). Вы можете проверить источник, из которого был скопирован упрощенный код, поскольку он фактически обрабатывает проверку и обновление хэшей, реализованных из предыдущего ответа, что полезно, если вам нужно увеличить iterCount в будущем.

Ответ 4

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

Из статьи на сайте Sitepoint:

Хакер все еще может выполнять что называется атакой словаря. Вредоносные стороны могут атака словаря, взяв, для экземпляр, 100 000 паролей, которые они Знают, что люди часто используют (например, город имена, спортивные команды и т.д.), хэш их, а затем сравнить каждую запись в словарь по каждой строке в таблица базы данных. Если хакеры находят матч, бинго! У них есть свой пароль. Однако для решения этой проблемы мы нужно только солить хэш.

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

В этом случае не только хакеру нужно угадать пароль, они тоже должны были угадать соль. Добавление соли к четкому тексту улучшает безопасность: теперь, если хакер пытается словарь атаковать, он должен хэш его 100 000 записей с солью каждого строка пользователя. Хотя он все еще возможно, шансы взлома успех радикально уменьшается.

В .NET нет метода, автоматически выполняющего это, поэтому вы должны перейти к решению выше.

Ответ 5

Я создал класс, который имеет следующий метод:

  1. Создать соль
  2. Вход хеша
  3. Подтвердить ввод

    public class CryptographyProcessor
    {
        public string CreateSalt(int size)
        {
            //Generate a cryptographic random number.
              RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
             byte[] buff = new byte[size];
             rng.GetBytes(buff);
             return Convert.ToBase64String(buff);
        }
    
    
          public string GenerateHash(string input, string salt)
          { 
             byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
             SHA256Managed sHA256ManagedString = new SHA256Managed();
             byte[] hash = sHA256ManagedString.ComputeHash(bytes);
             return Convert.ToBase64String(hash);
          }
    
          public bool AreEqual(string plainTextInput, string hashedInput, string salt)
          {
               string newHashedPin = GenerateHash(plainTextInput, salt);
               return newHashedPin.Equals(hashedInput); 
          }
     }
    

    '

Ответ 6

Ба, это лучше! http://sourceforge.net/projects/pwdtknet/, и это лучше, потому что..... он выполняет растягивание клавиш И использует HMACSHA512:)

Ответ 7

Я создал библиотеку SimpleHashing.Net, чтобы упростить процесс хэширования базовыми классами, предоставляемыми Microsoft. Обычное SHA на самом деле недостаточно для надежного хранения паролей.

В библиотеке используется идея хэш-формата из Bcrypt, но поскольку официальная реализация MS не используется, я предпочитаю использовать то, что доступно в фреймворке (например, PBKDF2), но это слишком сложно из коробки.

Это быстрый пример использования библиотеки:

ISimpleHash simpleHash = new SimpleHash();

// Creating a user hash, hashedPassword can be stored in a database
// hashedPassword contains the number of iterations and salt inside it similar to bcrypt format
string hashedPassword = simpleHash.Compute("Password123");

// Validating user password by first loading it from database by username
string storedHash = _repository.GetUserPasswordHash(username);
isPasswordValid = simpleHash.Verify("Password123", storedHash);

Ответ 8

Вот как я это делаю. Я создаю хэш и сохраняю его с помощью ProtectedData api:

    public static string GenerateKeyHash(string Password)
    {
        if (string.IsNullOrEmpty(Password)) return null;
        if (Password.Length < 1) return null;

        byte[] salt = new byte[20];
        byte[] key = new byte[20];
        byte[] ret = new byte[40];

        try
        {
            using (RNGCryptoServiceProvider randomBytes = new RNGCryptoServiceProvider())
            {
                randomBytes.GetBytes(salt);

                using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
                {
                    key = hashBytes.GetBytes(20);
                    Buffer.BlockCopy(salt, 0, ret, 0, 20);
                    Buffer.BlockCopy(key, 0, ret, 20, 20);
                }
            }
            // returns salt/key pair
            return Convert.ToBase64String(ret);
        }
        finally
        {
            if (salt != null)
                Array.Clear(salt, 0, salt.Length);
            if (key != null)
                Array.Clear(key, 0, key.Length);
            if (ret != null)
                Array.Clear(ret, 0, ret.Length);
        } 
    }

    public static bool ComparePasswords(string PasswordHash, string Password)
    {
        if (string.IsNullOrEmpty(PasswordHash) || string.IsNullOrEmpty(Password)) return false;
        if (PasswordHash.Length < 40 || Password.Length < 1) return false;

        byte[] salt = new byte[20];
        byte[] key = new byte[20];
        byte[] hash = Convert.FromBase64String(PasswordHash);

        try
        {
            Buffer.BlockCopy(hash, 0, salt, 0, 20);
            Buffer.BlockCopy(hash, 20, key, 0, 20);

            using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
            {
                byte[] newKey = hashBytes.GetBytes(20);

                if (newKey != null)
                    if (newKey.SequenceEqual(key))
                        return true;
            }
            return false;
        }
        finally
        {
            if (salt != null)
                Array.Clear(salt, 0, salt.Length);
            if (key != null)
                Array.Clear(key, 0, key.Length);
            if (hash != null)
                Array.Clear(hash, 0, hash.Length);
        }
    }

    public static byte[] DecryptData(string Data, byte[] Salt)
    {
        if (string.IsNullOrEmpty(Data)) return null;

        byte[] btData = Convert.FromBase64String(Data);

        try
        {
            return ProtectedData.Unprotect(btData, Salt, DataProtectionScope.CurrentUser);
        }
        finally
        {
            if (btData != null)
                Array.Clear(btData, 0, btData.Length);
        }
    }

    public static string EncryptData(byte[] Data, byte[] Salt)
    {
        if (Data == null) return null;
        if (Data.Length < 1) return null;

        byte[] buffer = new byte[Data.Length];

        try
        {
            Buffer.BlockCopy(Data, 0, buffer, 0, Data.Length);
            return System.Convert.ToBase64String(ProtectedData.Protect(buffer, Salt, DataProtectionScope.CurrentUser));
        }
        finally
        {
            if (buffer != null)
                Array.Clear(buffer, 0, buffer.Length);
        }
    }

Ответ 9

Я прочитал все ответы, и я думаю, что это достаточно, особенно статьи @Michael с медленным хэшированием и замечаниями @CodesInChaos, но я решил поделиться своим фрагментом кода для хэширования/проверки, который может быть полезен, и это не требует [ Microsoft.AspNet.Cryptography.KeyDerivation ].

    private static bool SlowEquals(byte[] a, byte[] b)
            {
                uint diff = (uint)a.Length ^ (uint)b.Length;
                for (int i = 0; i < a.Length && i < b.Length; i++)
                    diff |= (uint)(a[i] ^ b[i]);
                return diff == 0;
            }

    private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
            {
                Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
                pbkdf2.IterationCount = iterations;
                return pbkdf2.GetBytes(outputBytes);
            }

    private static string CreateHash(string value, int salt_bytes, int hash_bytes, int pbkdf2_iterations)
            {
                // Generate a random salt
                RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
                byte[] salt = new byte[salt_bytes];
                csprng.GetBytes(salt);

                // Hash the value and encode the parameters
                byte[] hash = PBKDF2(value, salt, pbkdf2_iterations, hash_bytes);

                //You need to return the salt value too for the validation process
                return Convert.ToBase64String(hash) + ":" + 
                       Convert.ToBase64String(hash);
            }

    private static bool ValidateHash(string pureVal, string saltVal, string hashVal, int pbkdf2_iterations)
            {
                try
                {
                    byte[] salt = Convert.FromBase64String(saltVal);
                    byte[] hash = Convert.FromBase64String(hashVal);

                    byte[] testHash = PBKDF2(pureVal, salt, pbkdf2_iterations, hash.Length);
                    return SlowEquals(hash, testHash);
                }
                catch (Exception ex)
                {
                    return false;
                }
            }

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

Ответ 10

Используйте пакет System.Web.Helpers.Crypto NuGet от Microsoft. Это автоматически добавляет соль к хешу.

Вы var hash = Crypto.HashPassword("foo"); пароль следующим образом: var hash = Crypto.HashPassword("foo");

Вы проверяете пароль следующим образом: var verified = Crypto.VerifyHashedPassword(hash, "foo");

Ответ 11

 protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e)
{
string salt =createSalt(10);
string hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt);
m_SaltHash_TextBox.Text=Salt;
 m_SaltSHA256Hash_TextBox.Text=hashedPassword;

}
 public string createSalt(int size)
{
 var rng= new System.Security.Cyptography.RNGCyptoServiceProvider();
 var buff= new byte[size];
rng.GetBytes(buff);
 return Convert.ToBase64String(buff);
}


 public string GenerateSHA256Hash(string input,string salt)
{
 byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt);
 new System.Security.Cyptography.SHA256Managed();
 byte[]hash=sha256hashString.ComputedHash(bytes);
 return bytesArrayToHexString(hash);
  }

Ответ 12

В ответ на эту часть исходного вопроса "Есть ли какой-либо другой метод С# для хэширования паролей". Вы можете достичь этого, используя ASP.NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity.EntityFramework/3.0.0-rc1-final

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using System.Security.Principal;

namespace HashTest{


    class Program
    {
        static void Main(string[] args)
        {

            WindowsIdentity wi = WindowsIdentity.GetCurrent();

            var ph = new PasswordHasher<WindowsIdentity>();

            Console.WriteLine(ph.HashPassword(wi,"test"));

            Console.WriteLine(ph.VerifyHashedPassword(wi,"AQAAAAEAACcQAAAAEA5S5X7dmbx/NzTk6ixCX+bi8zbKqBUjBhID3Dg1teh+TRZMkAy3CZC5yIfbLqwk2A==","test"));

        }
    }


}

Ответ 13

create proc [dbo].[hash_pass] @family nvarchar(50), @username nvarchar(50), @pass nvarchar(Max),``` @semat nvarchar(50), @tell nvarchar(50)

as insert into tbl_karbar values (@family,@username,(select HASHBYTES('SHA1' ,@pass)),@semat,@tell)