Hashing password con MD5 o sha-256 C #

Sto scrivendo un modulo di registrazione per un’applicazione, ma ho ancora problemi con l’essere nuovo di c #.

Sto cercando di crittografare / hash password per md5 o sha-256, preferibilmente sha-256.

Qualche buon esempio? Voglio che sia in grado di prendere le informazioni da “string password”; e poi cancellarlo e memorizzarlo nella variabile “stringa hPassword;”. Qualche idea?

Non usare un semplice hash o un hash salato. Utilizzare una sorta di tecnica di rafforzamento delle chiavi come bcrypt (con un’implementazione .NET qui ) o PBKDF2 (con un’implementazione integrata ).

Ecco un esempio che utilizza PBKDF2.

Per generare una chiave dalla tua password …

string password = GetPasswordFromUserInput(); // specify that we want to randomly generate a 20-byte salt using (var deriveBytes = new Rfc2898DeriveBytes(password, 20)) { byte[] salt = deriveBytes.Salt; byte[] key = deriveBytes.GetBytes(20); // derive a 20-byte key // save salt and key to database } 

E poi per verificare se una password è valida …

 string password = GetPasswordFromUserInput(); byte[] salt, key; // load salt and key from database using (var deriveBytes = new Rfc2898DeriveBytes(password, salt)) { byte[] newKey = deriveBytes.GetBytes(20); // derive a 20-byte key if (!newKey.SequenceEqual(key)) throw new InvalidOperationException("Password is invalid!"); } 

Stai per voler utilizzare lo spazio dei nomi System.Security.Cryptography ; in particolare, la class MD5 o la class SHA256 .

Disegnando un po ‘dal codice di questa pagina e con la consapevolezza che entrambe le classi hanno la stessa class base ( HashAlgorithm ), potresti usare una funzione come questa:

 public string ComputeHash(string input, HashAlgorithm algorithm) { Byte[] inputBytes = Encoding.UTF8.GetBytes(input); Byte[] hashedBytes = algorithm.ComputeHash(inputBytes); return BitConverter.ToString(hashedBytes); } 

Quindi puoi chiamarlo così (per MD5):

 string hPassword = ComputeHash(password, new MD5CryptoServiceProvider()); 

O per SHA256:

 string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider()); 

Modifica: aggiunta di supporto per il sale
Come sottolineato da dtb nei commenti, questo codice sarebbe più forte se includesse la possibilità di aggiungere sale . Se non si ha familiarità con esso, salt è un insieme di bit casuali che sono inclusi come input per la funzione hash, che è molto importante per contrastare gli attacchi di dizionario su una password con hash (ad esempio, utilizzando una tabella arcobaleno ). Ecco una versione modificata della funzione ComputeHash che supporta salt:

 public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt) { Byte[] inputBytes = Encoding.UTF8.GetBytes(input); // Combine salt and input bytes Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length]; salt.CopyTo(saltedInput, 0); inputBytes.CopyTo(saltedInput, salt.Length); Byte[] hashedBytes = algorithm.ComputeHash(saltedInput); return BitConverter.ToString(hashedBytes); } 

Spero che questo sia stato utile!

Si dovrebbe sempre salare la password prima dell’hashing quando li si archivia nel database.

Colonne del database consigliate:

  • PasswordSalt: int
  • PasswordHash: binary (20)

La maggior parte dei post che trovi online parleranno della codifica ASCII di sale e hash, ma non è necessaria e aggiunge solo calcoli non necessari. Inoltre, se si utilizza SHA-1 , l’output sarà di soli 20 byte, pertanto il campo hash nel database deve essere lungo solo 20 byte. Capisco la tua domanda su SHA-256, ma a meno che tu non abbia una ragione convincente, usare SHA-1 con un valore salato sarà sufficiente nella maggior parte delle pratiche commerciali. Se si insiste su SHA-256, il campo hash nel database deve avere una lunghezza di 32 byte.

Di seguito sono elencate alcune funzioni che generano il sale, calcolano l’hash e verificano l’hash con una password.

La funzione salt di seguito genera un sale crittograficamente forte come un intero da 4 byte casuali creati crittograficamente.

 private int GenerateSaltForPassword() { RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] saltBytes = new byte[4]; rng.GetNonZeroBytes(saltBytes); return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]); } 

La password può quindi essere sottoposta a hash utilizzando il sale con la funzione seguente. Il sale è concatenato alla password e quindi l'hash è calcolato.

 private byte[] ComputePasswordHash(string password, int salt) { byte[] saltBytes = new byte[4]; saltBytes[0] = (byte)(salt >> 24); saltBytes[1] = (byte)(salt >> 16); saltBytes[2] = (byte)(salt >> 8); saltBytes[3] = (byte)(salt); byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password); byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length]; System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length); System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length); SHA1 sha1 = SHA1.Create(); return sha1.ComputeHash(preHashed); } 

Il controllo della password può essere fatto semplicemente calcolando l'hash e confrontandolo con l'hash previsto.

 private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash) { byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt); return hashedPassword.SequenceEqual(correctPasswordHash); } 

Se stai memorizzando le password con hash, usa bcrypt invece di SHA-256. Il problema è che SHA-256 è ottimizzato per la velocità, il che rende più facile l’attacco brute force alle password se qualcuno dovesse accedere al tuo database.

Leggi questo articolo: Basta con le tabelle Rainbow: che cosa è necessario sapere sugli schemi di password sicura e questa risposta a una precedente domanda SO.

Alcune citazioni dall’articolo:

Il problema è che MD5 è veloce. Così sono i suoi concorrenti moderni, come SHA1 e SHA256. La velocità è un objective progettuale di un moderno hash sicuro, perché gli hash sono un elemento fondamentale di quasi tutti i sistemi di crittografia e in genere ottengono un’esecuzione su richiesta per pacchetto o per messaggio.

La velocità è esattamente ciò che non si desidera in una funzione di hash della password.


Infine, abbiamo appreso che se vogliamo memorizzare le password in modo sicuro, abbiamo tre opzioni ragionevoli: lo schema MD5 di PHK, lo schema Bcrypt di Provos-Maziere e SRP. Abbiamo appreso che la scelta corretta è Bcrypt.

PBKDF2 sta usando HMACSHA1 ……. se vuoi un’implementazione più moderna HMACSHA256 o HMACSHA512 e vuoi ancora allungare la chiave per rallentare l’algoritmo, ti suggerisco questa API: https://sourceforge.net/projects/pwdtknet/

Ecco una implementazione completa di una class SecuredPassword inconscia di persistenza

 using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; public class SecuredPassword { private const int saltSize = 256; private readonly byte[] hash; private readonly byte[] salt; public byte[] Hash { get { return hash; } } public byte[] Salt { get { return salt; } } public SecuredPassword(string plainPassword) { if (string.IsNullOrWhiteSpace(plainPassword)) return; using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize)) { salt = deriveBytes.Salt; hash = deriveBytes.GetBytes(saltSize); } } public SecuredPassword(byte[] hash, byte[] salt) { this.hash = hash; this.salt = salt; } public bool Verify(string password) { if (string.IsNullOrWhiteSpace(password)) return false; using (var deriveBytes = new Rfc2898DeriveBytes(password, salt)) { byte[] newKey = deriveBytes.GetBytes(saltSize); return newKey.SequenceEqual(hash); } } } 

E i test:

  public class SecuredPasswordTests { [Test] public void IsHashed_AsExpected() { var securedPassword = new SecuredPassword("password"); Assert.That(securedPassword.Hash, Is.Not.EqualTo("password")); Assert.That(securedPassword.Hash.Length, Is.EqualTo(256)); } [Test] public void Generates_Unique_Salt() { var securedPassword = new SecuredPassword("password"); var securedPassword2 = new SecuredPassword("password"); Assert.That(securedPassword.Salt, Is.Not.Null); Assert.That(securedPassword2.Salt, Is.Not.Null); Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt)); } [Test] public void Generates_Unique_Hash() { var securedPassword = new SecuredPassword("password"); var securedPassword2 = new SecuredPassword("password"); Assert.That(securedPassword.Hash, Is.Not.Null); Assert.That(securedPassword2.Hash, Is.Not.Null); Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash)); } [Test] public void Verify_WhenMatching_ReturnsTrue() { var securedPassword = new SecuredPassword("password"); var result = securedPassword.Verify("password"); Assert.That(result, Is.True); } [Test] public void Verify_WhenDifferent_ReturnsFalse() { var securedPassword = new SecuredPassword("password"); var result = securedPassword.Verify("Password"); Assert.That(result, Is.False); } [Test] public void Verify_WhenRehydrated_AndMatching_ReturnsTrue() { var securedPassword = new SecuredPassword("password123"); var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt); var result = rehydrated.Verify("password123"); Assert.That(result, Is.True); } [Test] public void Constructor_Handles_Null_Password() { Assert.DoesNotThrow(() => new SecuredPassword(null)); } [Test] public void Constructor_Handles_Empty_Password() { Assert.DoesNotThrow(() => new SecuredPassword(string.Empty)); } [Test] public void Verify_Handles_Null_Password() { Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null)); } [Test] public void Verify_Handles_Empty_Password() { Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty)); } [Test] public void Verify_When_Null_Password_ReturnsFalse() { Assert.That(new SecuredPassword("password").Verify(null), Is.False); } } 

La class System.Security.Cryptography.SHA256 dovrebbe fare il trucco:

http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx

Si prega di utilizzare questo come ho gli stessi problemi prima, ma potrebbe risolverlo sarà lo snippet di codice litle

  public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt) { Byte[] inputBytes = Encoding.UTF8.GetBytes(input); // Combine salt and input bytes Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length]; salt.CopyTo(saltedInput, 0); inputBytes.CopyTo(saltedInput, salt.Length); Byte[] hashedBytes = algorithm.ComputeHash(saltedInput); StringBuilder hex = new StringBuilder(hashedBytes.Length * 2); foreach (byte b in hashedBytes) hex.AppendFormat("{0:X2}", b); return hex.ToString(); } 

TL; DR utilizza Microsoft.AspNetCore.Cryptography.KeyDerivation , implementando PBKDF2 con SHA-512.

La buona idea per iniziare con hashing delle password è guardare a quali linee guida OWASP dicono. L’elenco degli algoritmi consigliati include Argon2, PBKDF2, scrypt e bcrypt. Tutti questi algoritmi possono essere regolati per regolare il tempo necessario per l’hash di una password e, corrispondentemente, il tempo per craccarlo tramite la forza bruta. Tutti questi algoritmi utilizzano il sale per proteggere dagli attacchi delle tabelle arcobaleno.

Nessuno di questi algoritmi è terribilmente debole, ma ci sono alcune differenze:

  • bcrypt è in circolazione da quasi 20 anni, è stato ampiamente utilizzato e ha resistito alla prova del tempo. È piuttosto resistente agli attacchi GPU, ma non all’FPGA
  • Argon2 è l’aggiunta più recente, essendo un vincitore della concorrenza 2015 di hashing delle password. Ha una protezione migliore contro gli attacchi GPU e FPGA, ma è un po ‘troppo recente di mio gradimento
  • Non so molto di scrypt. È stato progettato per contrastare gli attacchi accelerati GPU e FPGA, ma ho sentito che non è così forte come originariamente dichiarato
  • PBKDF2 è una famiglia di algoritmi parametrizzati dalle diverse funzioni hash. Non offre una protezione specifica contro gli attacchi GPU o ASIC, specialmente se viene utilizzata una funzione hash più debole come SHA-1, ma è comunque certificata FIPS se è importante per te e comunque accettabile se il numero di iterazioni è abbastanza grande.

Basato su algoritmi da solo, probabilmente andrei con bcrypt, essendo PBKDF2 il meno favorevole.

Tuttavia, non è la storia completa, perché anche il miglior algoritmo può essere reso insicuro da una ctriggers implementazione. Diamo un’occhiata a ciò che è disponibile per la piattaforma .NET:

  • Bcrypt è disponibile tramite bcrypt.net . Dicono che l’implementazione sia basata su jBCrypt Java. Attualmente ci sono 6 contributori e 8 numeri (tutti chiusi) su github. Nel complesso, sembra buono, tuttavia, non so se qualcuno ha effettuato un controllo del codice, ed è difficile dire se una versione aggiornata sarà disponibile abbastanza presto se viene rilevata una vulnerabilità. Ho sentito Stack Overflow allontanarsi dall’uso di bcrypt a causa di tali motivi
  • Probabilmente il modo migliore per usare Argon2 è attraverso i collegamenti alla libreria libsodium nota, ad esempio https://github.com/adamcaudill/libsodium-net . L’idea è che la maggior parte della crittografia è implementata tramite libsodium, che ha un notevole supporto, e le parti “non testate” sono piuttosto limitate. Tuttavia, nei dettagli di crittografia significa molto, quindi combinato con Argon2 è relativamente recente, lo tratterei come un’opzione sperimentale
  • Per molto tempo, .NET ha implementato un’implementazione di PBKDF2 tramite la class Rfc2898DeriveBytes . Tuttavia, l’implementazione può utilizzare solo la funzione hash SHA-1, che è considerata troppo veloce per essere al sicuro al giorno d’oggi
  • Infine, la soluzione più recente è il pacchetto Microsoft.AspNetCore.Cryptography.KeyDerivation disponibile tramite NuGet. Fornisce l’algoritmo PBKDF2 con le funzioni hash SHA-1, SHA-256 o SHA-512, che è Rfc2898DeriveBytes migliore di Rfc2898DeriveBytes . Il più grande vantaggio qui è che l’implementazione è fornita da Microsoft, e mentre non riesco a valutare correttamente la diligenza di crittografia degli sviluppatori Microsoft rispetto a BCrypt.net o agli sviluppatori di libsodium, ha senso fidarsi di essa perché se si esegue un’applicazione .NET, si stanno già facendo molto affidamento su Microsoft. Potremmo anche aspettarci che Microsoft rilasci gli aggiornamenti in caso di problemi di sicurezza. Fiduciosamente.

Per riassumere la ricerca fino a questo punto, mentre PBKDF2 potrebbe essere l’algoritmo meno preferito dei quattro, la disponibilità dell’implementazione fornita da Microsoft supera quella, quindi la decisione ragionevole sarebbe quella di utilizzare Microsoft.AspNetCore.Cryptography.KeyDerivation .

Il pacchetto recente al momento punta su .NET Standard 2.0, quindi disponibile in .NET Core 2.0 o .NET Framework 4.6.1 o successivo. Se si utilizza la versione precedente della framework, è ansible utilizzare la versione precedente del pacchetto, 1.1.3 , che ha come destinazione .NET Framework 4.5.1 o .NET Core 1.0. Sfortunatamente, non è ansible utilizzarlo anche nelle versioni precedenti di .NET.

La documentazione e l’esempio di lavoro sono disponibili su docs.microsoft.com . Tuttavia, non copiare e incollare così com’è, ci sono ancora decisioni che uno sviluppatore deve prendere.

La prima decisione è quale funzione hash usare. Le opzioni disponibili includono SHA-1, SHA-256 e SHA-512. Di questi, SHA-1 è decisamente troppo veloce per essere sicuro, SHA-256 è decente, ma io raccomanderei SHA-512, perché presumibilmente, l’uso delle sue operazioni a 64 bit rende più difficile beneficiare degli attacchi basati su GPU.

Quindi, è necessario scegliere la lunghezza dell’output hash della password e la lunghezza del sale. Non ha senso avere un output più lungo dell’output della funzione hash (ad esempio 512 bit per SHA-512), e probabilmente sarebbe il più sicuro di averlo esattamente così. Per la lunghezza del sale, le opinioni divergono. 128 bit dovrebbero essere sufficienti, ma in ogni caso, la lunghezza più lunga della lunghezza di output hash sicuramente non offre alcun vantaggio.

Successivamente, c’è un conteggio dell’iterazione. Più grande è, più difficili sono gli hash delle password, ma più tempo ci vuole per registrare gli utenti. Suggerirei di sceglierlo in modo che l’hashing impieghi 0,25-1 secondi sul tipico sistema di produzione, e in ogni caso, non dovrebbe essere inferiore a 10000.

Normalmente, si ottiene l’array di byte come valori di hash e di sale. Usa Base64 per convertirli in stringhe. È ansible scegliere di utilizzare due colonne diverse nel database o combinare sale e password in una colonna utilizzando un separatore che non si trova in Base64.

Non dimenticare di creare una memoria di hashing delle password in un modo che consenta di passare facilmente a un algoritmo di hashing migliore in futuro.