Risposte:
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 data
array di byte puoi usare
var data = Encoding.ASCII.GetBytes(password);
e per recuperare la stringa da md5data
osha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
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.
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 / Rfc2898DeriveBytes
per 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 100000
può essere ridotto. Un valore minimo dovrebbe essere intorno 10000
.
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 .
V1
e V2
quale metodo avete bisogno di verifica.
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);
}
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);
}
}
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);
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".
using
dichiarazione o di chiamarloClear()
quando hai finito di utilizzare l'implementazione.