Come eseguire l'hash di una password


117

Vorrei memorizzare l'hash di una password sul telefono, ma non sono sicuro di come farlo. Riesco a trovare solo metodi di crittografia. Come deve essere correttamente hash la password?

Risposte:


62

AGGIORNAMENTO : QUESTA RISPOSTA È SERIAMENTE SUPERATA . Si prega di utilizzare i consigli da https://stackoverflow.com/a/10402129/251311 .

Puoi usare

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

o

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

Per ottenere un dataarray di byte puoi usare

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

e per recuperare la stringa da md5dataosha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

11
Consiglio VERAMENTE di usare SHA1. MD5 è un no-no a meno che non si mantenga la retrocompatibilità con un sistema esistente. Inoltre, assicurati di inserirlo in una usingdichiarazione o di chiamarlo Clear()quando hai finito di utilizzare l'implementazione.
vcsjones

3
@vcsjones: non voglio fare la guerra santa qui, ma md5è abbastanza buono per quasi tutti i tipi di attività. Le sue vulnerabilità si riferiscono anche a situazioni molto specifiche e richiedono quasi che l'attaccante sappia molto sulla crittografia.
zerkms

4
@zerkms punto preso, ma se non c'è motivo di compatibilità con le versioni precedenti, non c'è motivo di usare MD5. "Meglio prevenire che curare".
vcsjones

4
Nessun motivo per utilizzare MD5 a questo punto. Dato che il tempo di calcolo è insignificante, non c'è motivo di utilizzare MD5 se non come compatibilità con i sistemi esistenti. Anche se MD5 è "abbastanza buono" non ci sono costi per l'utente, il più sicuro SHA. Sono sicuro che gli zerkms sappiano che questo commento è più per l'interrogante.
Gerald Davis

11
Tre grandi errori: 1) ASCII degrada silenziosamente le password con caratteri insoliti 2) Plain MD5 / SHA-1 / SHA-2 è veloce. 3) Hai bisogno di un sale. | Utilizza invece PBKDF2, bcrypt o scrypt. PBKDF2 è più semplice nella classe Rfc2898DeriveBytes (non so se presente su WP7)
CodesInChaos

298

La maggior parte delle altre risposte qui sono in qualche modo obsolete con le migliori pratiche odierne. In quanto tale, ecco l'applicazione per utilizzare PBKDF2 / Rfc2898DeriveBytesper archiviare e verificare le password. Il codice seguente è in una classe autonoma in questo post: Un altro esempio di come archiviare un hash di password salato . Le basi sono davvero facili, quindi qui è suddiviso:

PASSO 1 Crea il valore del sale con un PRNG crittografico:

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

PASSAGGIO 2 Crea il Rfc2898DeriveBytes e ottieni il valore hash:

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

PASSAGGIO 3 Combina i byte salt e password per un utilizzo successivo:

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

FASE 4 Trasforma la combinazione di sale + hash in una stringa per la conservazione

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

PASSAGGIO 5 Verificare la password immessa dall'utente rispetto a una password memorizzata

/* 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: a seconda dei requisiti di prestazione dell'applicazione specifica, il valore 100000può essere ridotto. Un valore minimo dovrebbe essere intorno 10000.


8
@Daniel fondamentalmente il post riguarda l'utilizzo di qualcosa di più sicuro di un hash da solo. Se si esegue semplicemente l'hashing di una password, anche con salt, le password degli utenti verranno compromesse (e probabilmente vendute / pubblicate) prima ancora che tu abbia la possibilità di dirgli di cambiarla. Usa il codice sopra per rendere difficile per l'attaccante, non facile per lo sviluppatore.
csharptest.net

2
@DatVM No, nuovo sale per ogni volta che immagazzini un hash. ecco perché è combinato con l'hash per l'archiviazione in modo da poter verificare una password.
csharptest.net

9
@CiprianJijie il punto è che non dovresti essere in grado di farlo.
csharptest.net

9
Nel caso in cui qualcuno stia utilizzando un metodo VerifyPassword, se desideri utilizzare Linq e una chiamata più breve per un booleano, questo dovrebbe: return hash.SequenceEqual (hashBytes.Skip (_saltSize));
Jesú Castillo

2
@ csharptest.net Che tipo di dimensioni di array consigliate? la dimensione dell'array influisce comunque molto sulla sicurezza? Non so molto di hashing / crittografia
lennyy

71

Sulla base dell'ottima risposta di csharptest.net , ho scritto una classe per questo:

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

Un hash di esempio potrebbe essere questo:

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

Come puoi vedere, ho anche incluso le iterazioni nell'hash per un facile utilizzo e la possibilità di aggiornarlo, se abbiamo bisogno di aggiornare.


Se sei interessato a .net core, ho anche una versione .net core su Code Review .


1
Solo per verificare, se aggiorni il motore di hashing, aumenteresti la sezione V1 del tuo hash e la toglieresti?
Mike Cole

1
Sì, questo è il piano. Si potrebbe poi decidere sulla base V1e V2quale metodo avete bisogno di verifica.
Christian Gollhardt

Grazie per la risposta e per la lezione. Lo sto implementando mentre parliamo.
Mike Cole

2
Sì @NelsonSilva. È a causa del sale .
Christian Gollhardt

1
Con tutto il copia / incolla di questo codice (incluso me), spero che qualcuno parli e il post venga rivisto se viene riscontrato un problema con esso! :)
pettys

14

Uso un hash e un salt per la crittografia della password (è lo stesso hash utilizzato dall'appartenenza ad 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);
}

16
-1 per usare SHA-1 semplice, che è veloce. Utilizza una funzione di derivazione della chiave lenta, come PBKDF2, bcrypt o scrypt.
CodesInChaos

1
  1. Crea un sale,
  2. Crea una password hash con salt
  3. Salva hash e sale
  4. decifrare con password e salt ... quindi gli sviluppatori non possono decrittografare la password
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

Le risposte di @ csharptest.net e Christian Gollhardt sono fantastiche, grazie mille. Ma dopo aver eseguito questo codice in produzione con milioni di record, ho scoperto che c'è una perdita di memoria. Le classi RNGCryptoServiceProvider e Rfc2898DeriveBytes derivano da IDisposable ma non le eliminiamo . Scriverò la mia soluzione come risposta se qualcuno ha bisogno della versione eliminata.

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

Penso che usare KeyDerivation.Pbkdf2 sia migliore di Rfc2898DeriveBytes.

Esempio e spiegazione: password hash in 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=
 */

Questo è un codice di esempio tratto dall'articolo. Ed è un livello di sicurezza minimo. Per aumentarlo userei invece del parametro KeyDerivationPrf.HMACSHA1

KeyDerivationPrf.HMACSHA256 o KeyDerivationPrf.HMACSHA512.

Non scendere a compromessi sull'hashing delle password. Esistono molti metodi matematicamente validi per ottimizzare l'hacking dell'hash delle password. Le conseguenze potrebbero essere disastrose. Una volta che un malfattore può mettere le mani sulla tabella hash delle password dei tuoi utenti, sarebbe relativamente facile per lui decifrare le password dato che l'algoritmo è debole o l'implementazione non è corretta. Ha molto tempo (tempo x potenza del computer) per decifrare le password. L'hashing della password dovrebbe essere crittograficamente forte per trasformare "molto tempo" in " quantità di tempo irragionevole ".

Un altro punto da aggiungere

La verifica dell'hash richiede tempo (ed è buona). Quando l'utente inserisce un nome utente sbagliato, non ci vuole tempo per verificare che il nome utente non sia corretto. Quando il nome utente è corretto, iniziamo la verifica della password: è un processo relativamente lungo.

Per un hacker sarebbe molto facile capire se l'utente esiste o no.

Assicurati di non restituire una risposta immediata quando il nome utente è sbagliato.

Inutile dire: non rispondere mai a ciò che non va. Solo generale "Le credenziali sono sbagliate".


1
A proposito, la risposta precedente stackoverflow.com/a/57508528/11603057 non è corretta e dannosa. Questo è un esempio di hashing, non di hashing della password. Devono essere iterazioni della funzione pseudo-casuale durante il processo di derivazione della chiave. Non c'è. Non posso commentarlo o downvote (la mia bassa reputazione). Per favore, non perdere risposte errate!
Albert Lyubarsky
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.