Jawaban:
PEMBARUAN : JAWABAN INI SANGAT KELUAR . Silakan gunakan rekomendasi dari https://stackoverflow.com/a/10402129/251311 sebagai gantinya.
Anda bisa menggunakan
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
atau
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
Untuk mendapatkan data
array byte bisa Anda gunakan
var data = Encoding.ASCII.GetBytes(password);
dan untuk mendapatkan kembali string dari md5data
atausha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
md5
cukup baik untuk hampir semua jenis tugas. Kerentanannya juga mengacu pada situasi yang sangat spesifik dan hampir mengharuskan penyerang untuk mengetahui banyak tentang kriptografi.
Sebagian besar jawaban lain di sini agak ketinggalan zaman dengan praktik terbaik saat ini. Seperti inilah aplikasi menggunakan PBKDF2 / Rfc2898DeriveBytes
untuk menyimpan dan memverifikasi password. Kode berikut berada di kelas yang berdiri sendiri di posting ini: Contoh lain tentang cara menyimpan hash kata sandi asin . Dasar-dasarnya sangat mudah, jadi inilah uraiannya:
LANGKAH 1 Buat nilai garam dengan PRNG kriptografi:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
LANGKAH 2 Buat Rfc2898DeriveBytes dan dapatkan nilai hash:
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
LANGKAH 3 Gabungkan byte garam dan sandi untuk digunakan nanti:
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
LANGKAH 4 Ubah gabungan garam + hash menjadi string untuk disimpan
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
LANGKAH 5 Verifikasi kata sandi yang dimasukkan pengguna dengan kata sandi yang disimpan
/* 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();
Catatan: Bergantung pada persyaratan kinerja aplikasi spesifik Anda, nilainya 100000
dapat dikurangi. Nilai minimum harus sekitar 10000
.
Berdasarkan jawaban bagus csharptest.net , saya telah menulis Kelas untuk ini:
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;
}
}
Pemakaian:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Contoh hash bisa jadi ini:
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
Seperti yang Anda lihat, saya juga telah memasukkan iterasi dalam hash untuk penggunaan yang mudah dan kemungkinan untuk meningkatkan ini, jika kita perlu meningkatkan.
Jika Anda tertarik dengan .net core, saya juga memiliki versi inti .net di Code Review .
V1
dan V2
metode verifikasi mana yang Anda butuhkan.
Saya menggunakan hash dan salt untuk enkripsi kata sandi saya (hash yang sama dengan yang digunakan oleh Asp.Net Membership):
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);
}
}
Jawaban @ csharptest.net dan Christian Gollhardt sangat bagus, terima kasih banyak. Tetapi setelah menjalankan kode ini pada produksi dengan jutaan catatan, saya menemukan ada kebocoran memori. Kelas RNGCryptoServiceProvider dan Rfc2898DeriveBytes diturunkan dari IDisposable tetapi kami tidak membuangnya. Saya akan menulis solusi saya sebagai jawaban jika seseorang membutuhkan dengan versi yang dibuang.
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;
}
}
}
Pemakaian:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Saya pikir menggunakan KeyDerivation.Pbkdf2 lebih baik daripada Rfc2898DeriveBytes.
Contoh dan penjelasan: Kata sandi hash di 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=
*/
Ini adalah contoh kode dari artikel. Dan itu adalah tingkat keamanan minimum. Untuk meningkatkannya, saya akan menggunakan parameter KeyDerivationPrf.HMACSHA1
KeyDerivationPrf.HMACSHA256 atau KeyDerivationPrf.HMACSHA512.
Jangan berkompromi dengan hashing kata sandi. Ada banyak metode yang terdengar secara matematis untuk mengoptimalkan peretasan hash kata sandi. Konsekuensinya bisa menjadi bencana. Setelah penjahat bisa mendapatkan tabel hash kata sandi pengguna Anda, akan relatif mudah baginya untuk memecahkan kata sandi karena algoritme lemah atau penerapannya salah. Dia punya banyak waktu (waktu x kekuatan komputer) untuk memecahkan kata sandi. Pencirian sandi harus kuat secara kriptografis untuk mengubah "banyak waktu" menjadi "waktu yang tidak wajar ".
Satu hal lagi untuk ditambahkan
Verifikasi hash membutuhkan waktu (dan itu bagus). Ketika pengguna memasukkan nama pengguna yang salah, tidak perlu waktu lama untuk memeriksa bahwa nama pengguna itu salah. Ketika nama pengguna benar, kami memulai verifikasi kata sandi - ini proses yang relatif lama.
Bagi seorang hacker akan sangat mudah untuk memahami apakah pengguna itu ada atau tidak.
Pastikan untuk tidak langsung memberikan jawaban jika nama pengguna salah.
Tak perlu dikatakan: jangan pernah memberikan jawaban apa yang salah. Hanya umum "Kredensial salah".
using
pernyataan atau memanggilnyaClear()
setelah selesai menggunakan implementasi.