Хэш и соль пароли в C # - PullRequest
       88

Хэш и соль пароли в C #

166 голосов
/ 26 января 2010

Я только что просматривал одну из статей Дэвида Хейдена о Хешировании паролей пользователя .

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

Вот его код:

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

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

Ответы [ 12 ]

237 голосов
/ 26 января 2010

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

В моей книге Начиная с 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;
}

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

43 голосов
/ 06 января 2012

То, что сказал Blowdart, но с немного меньшим количеством кода. Используйте 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 для хеширования пароля (исходный код здесь ).

29 голосов
/ 31 мая 2013

Я читал, что такие функции хеширования, как 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]));
}

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

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

24 голосов
/ 26 января 2010

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

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

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

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

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

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

6 голосов
/ 17 июля 2018

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

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

    `

5 голосов
/ 23 августа 2013

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

3 голосов
/ 18 ноября 2016

Я создал библиотеку 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's password by first loading it from database by username
string storedHash = _repository.GetUserPasswordHash(username);
isPasswordValid = simpleHash.Verify("Password123", storedHash);
2 голосов
/ 06 марта 2014

Вот как я это делаю .. Я создаю хеш и сохраняю его, используя 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);
        }
    }
1 голос
/ 16 июня 2018

Я прочитал все ответы и думаю, что достаточно, особенно @ 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, которая так важна. Наконец, я надеюсь, что это поможет, и, пожалуйста, не стесняйтесь, советуйте мне о лучших подходах.

0 голосов
/ 24 апреля 2018
 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);
  }
...