Como fazer hash de uma senha


117

Gostaria de armazenar o hash de uma senha no telefone, mas não tenho certeza de como fazer isso. Só consigo encontrar métodos de criptografia. Como a senha deve ser hash corretamente?

Respostas:


62

ATUALIZAÇÃO : ESTA RESPOSTA ESTÁ SERIAMENTE DESATIVADA . Use as recomendações de https://stackoverflow.com/a/10402129/251311 .

Você pode usar

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

ou

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

Para obter uma datamatriz de bytes, você pode usar

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

e para recuperar a corda de md5dataousha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

11
Eu realmente recomendaria usar SHA1. MD5 é proibido, a menos que você mantenha a compatibilidade com versões anteriores de um sistema existente. Além disso, certifique-se de colocá-lo em uma usinginstrução ou chamá Clear()-lo quando terminar de usar a implementação.
vcsjones

3
@vcsjones: Não quero uma guerra santa aqui, mas md5é bom o suficiente para quase todos os tipos de tarefas. Suas vulnerabilidades também se referem a situações muito específicas e quase exigem que o invasor saiba muito sobre criptografia.
zerkms de

4
@zerkms ponto assumido, mas se não houver razão para compatibilidade com versões anteriores, não há razão para usar MD5. "Melhor prevenir do que remediar".
vcsjones de

4
Não há razão para usar MD5 neste momento. Dado que o tempo de computação é insignificante, não há razão para usar MD5, exceto como compatibilidade com sistemas existentes. Mesmo que o MD5 seja "bom o suficiente", não há custo para o usuário, o SHA muito mais seguro. Tenho certeza de que os zerkms sabem que o comentário é mais para o questionador.
Gerald Davis

11
Três grandes erros: 1) ASCII degrada silenciosamente as senhas com caracteres incomuns 2) MD5 / SHA-1 / SHA-2 simples é rápido. 3) Você precisa de um sal. | Use PBKDF2, bcrypt ou scrypt em seu lugar. PBKDF2 é mais fácil na classe Rfc2898DeriveBytes (não tenho certeza se estiver presente no WP7)
CodesInChaos

298

A maioria das outras respostas aqui está um tanto desatualizada com as melhores práticas de hoje. Como tal, aqui está a aplicação de PBKDF2 / Rfc2898DeriveBytespara armazenar e verificar senhas. O código a seguir está em uma classe autônoma nesta postagem: Outro exemplo de como armazenar um hash de senha com salt . O básico é muito fácil, então aqui está dividido:

PASSO 1 Crie o valor de sal com um PRNG criptográfico:

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

ETAPA 2 Crie o Rfc2898DeriveBytes e obtenha o valor de hash:

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

PASSO 3 Combine os bytes salt e password para uso posterior:

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

PASSO 4 Transforme o sal + hash combinado em uma string para armazenamento

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

ETAPA 5 Verifique a senha inserida pelo usuário em relação a uma senha armazenada

/* 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, 100000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
    if (hashBytes[i+16] != hash[i])
        throw new UnauthorizedAccessException();

Nota: Dependendo dos requisitos de desempenho de sua aplicação específica, o valor 100000pode ser reduzido. Um valor mínimo deve ser próximo 10000.


8
@Daniel basicamente, a postagem é sobre como usar algo mais seguro do que apenas um hash. Se você simplesmente hash uma senha, mesmo com salt, as senhas de seus usuários serão comprometidas (e provavelmente vendidas / publicadas) antes mesmo de você ter a chance de dizer a eles para alterá-la. Use o código acima para tornar isso difícil para o invasor, o que não é fácil para o desenvolvedor.
csharptest.net

2
@DatVM Não, novo sal para cada vez que você armazena um hash. é por isso que ele é combinado com o hash para armazenamento para que você possa verificar uma senha.
csharptest.net

9
@CiprianJijie a questão toda é que você não deve ser capaz.
csharptest.net

9
Caso alguém esteja fazendo um método VerifyPassword, se você gostaria de usar Linq e uma chamada mais curta para um booleano, isso faria: return hash.SequenceEqual (hashBytes.Skip (_saltSize));
Jesú Castillo

2
@ csharptest.net Que tipo de tamanho de array você recomenda? o tamanho do array afeta muito a segurança? Não sei muito sobre hashing / criptografia
lennyy

71

Com base na ótima resposta do csharptest.net , escrevi uma aula para isso:

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

Uso:

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

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

Um hash de amostra pode ser este:

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

Como você pode ver, também incluí as iterações no hash para facilitar o uso e a possibilidade de atualizá-lo, se precisarmos atualizar.


Se você estiver interessado em .net core, também tenho uma versão .net core no Code Review .


1
Apenas para verificar, se você atualizar o mecanismo de hash, você incrementará a seção V1 do seu hash e extrairá dela?
Mike Cole

1
Sim, esse é o plano. Você, então, decidir com base em V1e V2qual o método que você precisa de verificação.
Christian Gollhardt

Obrigado pela resposta e pela classe. Estou implementando enquanto falamos.
Mike Cole

2
Sim @NelsonSilva. Isso é por causa do sal .
Christian Gollhardt

1
Com toda a cópia / colagem deste código (incluindo eu), espero que alguém se manifeste e a postagem seja revisada se for encontrado algum problema com ela! :)
pettys de

14

Eu uso um hash e um salt para a criptografia de minha senha (é o mesmo hash que o Asp.Net Membership usa):

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

16
-1 para usar SHA-1 simples, que é rápido. Use uma função de derivação de chave lenta, como PBKDF2, bcrypt ou scrypt.
CodesInChaos

1
  1. Crie um sal,
  2. Crie uma senha hash com salt
  3. Economize hash e sal
  4. descriptografar com senha e sal ... para que os desenvolvedores não possam descriptografar a senha
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); 
      }
 }

1

As respostas de @csharptest.net e Christian Gollhardt são ótimas, muito obrigado. Mas depois de executar esse código em produção com milhões de registros, descobri que há um vazamento de memória. As classes RNGCryptoServiceProvider e Rfc2898DeriveBytes são derivadas de IDisposable, mas não as descartamos. Escreverei minha solução como resposta caso alguém precise com versão descartada.

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
        using (var rng = new RNGCryptoServiceProvider())
        {
            byte[] salt;
            rng.GetBytes(salt = new byte[SaltSize]);
            using (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 $"$HASH|V1${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("HASH|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("$HASH|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
        using (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;
        }

    }
}

Uso:

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

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

0

Acho que usar KeyDerivation.Pbkdf2 é melhor do que Rfc2898DeriveBytes.

Exemplo e explicação: senhas de hash no 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=
 */

Este é um exemplo de código do artigo. E é um nível mínimo de segurança. Para aumentá-lo, eu usaria em vez do parâmetro KeyDerivationPrf.HMACSHA1

KeyDerivationPrf.HMACSHA256 ou KeyDerivationPrf.HMACSHA512.

Não comprometa o hash de senha. Existem muitos métodos matematicamente sólidos para otimizar o hack de senha. As consequências podem ser desastrosas. Uma vez que um malfeitor pode colocar as mãos na tabela de hash de senha de seus usuários, seria relativamente fácil para ele quebrar as senhas, dado que o algoritmo é fraco ou a implementação está incorreta. Ele tem muito tempo (tempo x capacidade do computador) para decifrar senhas. O hash de senha deve ser criptograficamente forte para transformar "muito tempo" em " quantidade de tempo irracional ".

Mais um ponto a adicionar

A verificação de hash leva tempo (e é bom). Quando o usuário insere um nome de usuário incorreto, não leva tempo para verificar se o nome de usuário está incorreto. Quando o nome de usuário está correto, iniciamos a verificação de senha - é um processo relativamente longo.

Para um hacker, seria muito fácil entender se o usuário existe ou não.

Certifique-se de não retornar uma resposta imediata quando o nome do usuário estiver errado.

Nem é preciso dizer: nunca responda o que está errado. Apenas "credenciais erradas" gerais.


1
BTW, a resposta anterior stackoverflow.com/a/57508528/11603057 não é correta e prejudicial. Esse é um exemplo de hashing, não hashing de senha. Devem ser iterações da função pseudo-aleatória durante o processo de derivação da chave. Não há. Não posso comentar ou votar negativamente (minha baixa reputação). Por favor, não perca respostas incorretas!
Albert Lyubarsky
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.