Как хешировать пароль

Я хотел бы сохранить хэш пароля на телефоне, но я не уверен, как это сделать. Я могу только найти способы шифрования. Как правильно хешировать пароль?

Ответ 1

ОБНОВЛЕНИЕ: НАСТОЯЩИЙ ОТВЕТ СЕРЬЕЗНО ВЫРАЖЕННЫЙ. Вместо этого используйте рекомендации fooobar.com/questions/42351/....

Вы можете использовать

var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);

или

var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);

Чтобы получить data в качестве байтового массива, вы можете использовать

var data = Encoding.ASCII.GetBytes(password);

и вернуть строку из md5data или sha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

Ответ 2

Большинство других ответов здесь несколько устарели от сегодняшних лучших практик. Таким образом, здесь используется приложение PBKDF2/Rfc2898DeriveBytes для хранения и проверки паролей. Следующий код находится в автономном классе в этом сообщении: Еще один пример того, как хранить хэш-листы соленых данных. Основы очень легкие, поэтому здесь он разбит:

ШАГ 1 Создайте значение соли с помощью криптографического PRNG:

byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

ШАГ 2 Создайте Rfc2898DeriveBytes и получите значение хеша:

var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] hash = pbkdf2.GetBytes(20);

ШАГ 3 Объедините байты соли и пароля для последующего использования:

byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);

ШАГ 4 Поверните объединенную соль + хэш в строку для хранения

string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });

ШАГ 5 Проверьте введенный пользователем пароль на сохраненный пароль

/* Fetch the stored value */
string savedPasswordHash = DBContext.GetUser(u => u.UserName == user).Password;
/* Extract the bytes */
byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);
/* Get the salt */
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
/* Compute the hash on the password the user entered */
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
    if (hashBytes[i+16] != hash[i])
        throw new UnauthorizedAccessException();

Примечание. В зависимости от требований к производительности вашего конкретного приложения значение "10000" может быть уменьшено. Минимальное значение должно быть около 1000.

Ответ 3

Основываясь на csharptest.net отличном ответе, я написал класс для этого:

public static class SecurePasswordHasher
{
    /// <summary>
    /// Size of salt.
    /// </summary>
    private const int SaltSize = 16;

    /// <summary>
    /// Size of hash.
    /// </summary>
    private const int HashSize = 20;

    /// <summary>
    /// Creates a hash from a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="iterations">Number of iterations.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password, int iterations)
    {
        // Create salt
        byte[] salt;
        new RNGCryptoServiceProvider().GetBytes(salt = new byte[SaltSize]);

        // Create hash
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
        var hash = pbkdf2.GetBytes(HashSize);

        // Combine salt and hash
        var hashBytes = new byte[SaltSize + HashSize];
        Array.Copy(salt, 0, hashBytes, 0, SaltSize);
        Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);

        // Convert to base64
        var base64Hash = Convert.ToBase64String(hashBytes);

        // Format hash with extra information
        return string.Format("$MYHASH$V1${0}${1}", iterations, base64Hash);
    }

    /// <summary>
    /// Creates a hash from a password with 10000 iterations
    /// </summary>
    /// <param name="password">The password.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password)
    {
        return Hash(password, 10000);
    }

    /// <summary>
    /// Checks if hash is supported.
    /// </summary>
    /// <param name="hashString">The hash.</param>
    /// <returns>Is supported?</returns>
    public static bool IsHashSupported(string hashString)
    {
        return hashString.Contains("$MYHASH$V1$");
    }

    /// <summary>
    /// Verifies a password against a hash.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="hashedPassword">The hash.</param>
    /// <returns>Could be verified?</returns>
    public static bool Verify(string password, string hashedPassword)
    {
        // Check hash
        if (!IsHashSupported(hashedPassword))
        {
            throw new NotSupportedException("The hashtype is not supported");
        }

        // Extract iteration and Base64 string
        var splittedHashString = hashedPassword.Replace("$MYHASH$V1$", "").Split('$');
        var iterations = int.Parse(splittedHashString[0]);
        var base64Hash = splittedHashString[1];

        // Get hash bytes
        var hashBytes = Convert.FromBase64String(base64Hash);

        // Get salt
        var salt = new byte[SaltSize];
        Array.Copy(hashBytes, 0, salt, 0, SaltSize);

        // Create hash with given salt
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
        byte[] hash = pbkdf2.GetBytes(HashSize);

        // Get result
        for (var i = 0; i < HashSize; i++)
        {
            if (hashBytes[i + SaltSize] != hash[i])
            {
                return false;
            }
        }
        return true;
    }
}

Использование:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

Пример хэша может быть таким:

$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn

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


Если вас интересует ядро .net, у меня также есть версия ядра .net на Code Review.

Ответ 4

Я использую хэш и соль для моего шифрования пароля (это тот же хеш, что и членство Asp.Net):

private string PasswordSalt
{
   get
   {
      var rng = new RNGCryptoServiceProvider();
      var buff = new byte[32];
      rng.GetBytes(buff);
      return Convert.ToBase64String(buff);
   }
}

private string EncodePassword(string password, string salt)
{
   byte[] bytes = Encoding.Unicode.GetBytes(password);
   byte[] src = Encoding.Unicode.GetBytes(salt);
   byte[] dst = new byte[src.Length + bytes.Length];
   Buffer.BlockCopy(src, 0, dst, 0, src.Length);
   Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
   HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
   byte[] inarray = algorithm.ComputeHash(dst);
   return Convert.ToBase64String(inarray);
}

Ответ 5

  1. Создайте соль,
  2. Создать хеш-пароль с солью
  3. Сохраните хэш и соль
  4. расшифровать с паролем и солью... так что разработчики не могут расшифровать пароль
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

Я думаю, что использование KeyDerivation.Pbkdf2 лучше, чем Rfc2898DeriveBytes.

Пример и объяснение: Хэш-пароли в ASP.NET Core

using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
 
public class Program
{
    public static void Main(string[] args)
    {
        Console.Write("Enter a password: ");
        string password = Console.ReadLine();
 
        // generate a 128-bit salt using a secure PRNG
        byte[] salt = new byte[128 / 8];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
        }
        Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
 
        // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
        string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
            password: password,
            salt: salt,
            prf: KeyDerivationPrf.HMACSHA1,
            iterationCount: 10000,
            numBytesRequested: 256 / 8));
        Console.WriteLine($"Hashed: {hashed}");
    }
}
 
/*
 * SAMPLE OUTPUT
 *
 * Enter a password: Xtw9NMgx
 * Salt: NZsP6NnmfBuYeJrrAKNuVQ==
 * Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
 */

Это пример кода из статьи. И это минимальный уровень безопасности. Чтобы увеличить его, я бы использовал вместо параметра KeyDerivationPrf.HMACSHA1

KeyDerivationPrf.HMACSHA256 или KeyDerivationPrf.HMACSHA512.

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

Еще один момент, чтобы добавить

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

Для хакера было бы очень легко понять, существует ли пользователь или нет.

Не возвращайте немедленный ответ при неправильном имени пользователя.

Нечего и говорить: никогда не отвечай, что не так. Просто общий "Полномочия неверны".