ฉันต้องการเก็บแฮชของรหัสผ่านไว้ในโทรศัพท์ แต่ไม่แน่ใจว่าต้องทำอย่างไร ดูเหมือนฉันจะพบวิธีการเข้ารหัสเท่านั้น ควรแฮชรหัสผ่านอย่างไรให้ถูกต้อง?
ฉันต้องการเก็บแฮชของรหัสผ่านไว้ในโทรศัพท์ แต่ไม่แน่ใจว่าต้องทำอย่างไร ดูเหมือนฉันจะพบวิธีการเข้ารหัสเท่านั้น ควรแฮชรหัสผ่านอย่างไรให้ถูกต้อง?
คำตอบ:
UPDATE : คำตอบนี้จะล้าสมัยอย่างจริงจัง โปรดใช้คำแนะนำจากhttps://stackoverflow.com/a/10402129/251311แทน
คุณสามารถใช้
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
หรือ
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
เพื่อให้ได้data
เป็นอาร์เรย์ไบต์คุณสามารถใช้ได้
var data = Encoding.ASCII.GetBytes(password);
และรับสตริงกลับจากmd5data
หรือsha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
md5
ก็ดีพอสำหรับงานเกือบทุกประเภท ช่องโหว่ของมันยังอ้างถึงสถานการณ์ที่เฉพาะเจาะจงมากและเกือบจะต้องให้ผู้โจมตีรู้มากเกี่ยวกับการเข้ารหัส
คำตอบอื่น ๆ ส่วนใหญ่ที่นี่ค่อนข้างล้าสมัยสำหรับแนวทางปฏิบัติที่ดีที่สุดในปัจจุบัน ด้วยเหตุนี้จึงเป็นการประยุกต์ใช้ PBKDF2 / Rfc2898DeriveBytes
เพื่อจัดเก็บและตรวจสอบรหัสผ่าน รหัสต่อไปนี้อยู่ในคลาสแบบสแตนด์อะโลนในโพสต์นี้: อีกตัวอย่างหนึ่งของวิธีการจัดเก็บแฮชรหัสผ่านแบบไม่ใช้ พื้นฐานนั้นง่ายมากดังนั้นจึงแบ่งออกเป็น:
ขั้นตอนที่ 1สร้างค่าเกลือด้วย PRNG เข้ารหัส:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
ขั้นตอนที่ 2สร้าง Rfc2898DeriveBytes และรับค่าแฮช:
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
ขั้นตอนที่ 3รวมไบต์เกลือและรหัสผ่านเพื่อใช้ในภายหลัง:
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
ขั้นตอนที่ 4เปลี่ยนเกลือ + แฮชรวมกันเป็นสตริงสำหรับจัดเก็บ
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
ขั้นตอนที่ 5ตรวจสอบรหัสผ่านที่ผู้ใช้ป้อนกับรหัสผ่านที่จัดเก็บไว้
/* 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();
หมายเหตุ: ขึ้นอยู่กับข้อกำหนดด้านประสิทธิภาพของแอปพลิเคชันเฉพาะของคุณค่า100000
สามารถลดลงได้ 10000
มูลค่าขั้นต่ำควรจะอยู่ที่ประมาณ
จากคำตอบที่ยอดเยี่ยมของ csharptest.netฉันได้เขียนคลาสสำหรับสิ่งนี้:
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;
}
}
การใช้งาน:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
แฮชตัวอย่างอาจเป็นดังนี้:
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
อย่างที่คุณเห็นฉันได้รวมการทำซ้ำไว้ในแฮชเพื่อการใช้งานที่ง่ายและความเป็นไปได้ในการอัปเกรดสิ่งนี้หากเราต้องการอัปเกรด
หากคุณมีความสนใจในหลัก .net, ฉันยังมีรุ่น .net หลักในรหัสตรวจสอบ
V1
และV2
วิธีการยืนยันที่คุณต้องการ
ฉันใช้แฮชและเกลือสำหรับการเข้ารหัสรหัสผ่านของฉัน (เป็นแฮชเดียวกับที่ 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);
}
}
ฉันคิดว่าการใช้ KeyDerivation.Pbkdf2 นั้นดีกว่า Rfc2898DeriveBytes
ตัวอย่างและคำอธิบาย: รหัสผ่านแฮชใน 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=
*/
นี่คือโค้ดตัวอย่างจากบทความ และเป็นระดับความปลอดภัยขั้นต่ำ เพื่อเพิ่มมันฉันจะใช้แทนพารามิเตอร์ KeyDerivationPrf.HMACSHA1
KeyDerivationPrf.HMACSHA256 หรือ KeyDerivationPrf.HMACSHA512
อย่าประนีประนอมกับการแฮชรหัสผ่าน มีวิธีการทางคณิตศาสตร์หลายวิธีในการเพิ่มประสิทธิภาพการแฮ็กแฮชรหัสผ่าน ผลที่ตามมาอาจเป็นหายนะ เมื่อผู้ร้ายสามารถรับมือกับตารางแฮชรหัสผ่านของผู้ใช้ของคุณได้มันจะค่อนข้างง่ายสำหรับเขาที่จะถอดรหัสรหัสผ่านเนื่องจากอัลกอริทึมอ่อนแอหรือการนำไปใช้งานไม่ถูกต้อง เขามีเวลามาก (เวลา x กำลังคอมพิวเตอร์) ในการถอดรหัสรหัสผ่าน การแฮชรหัสผ่านควรมีความเข้มงวดในการเข้ารหัสเพื่อเปลี่ยน "เวลามาก" เป็น "ระยะเวลาที่ไม่สมเหตุสมผล "
อีกหนึ่งจุดที่จะเพิ่ม
การตรวจสอบแฮชต้องใช้เวลา (และเป็นเรื่องดี) เมื่อผู้ใช้ป้อนชื่อผู้ใช้ผิดจะใช้เวลาไม่นานในการตรวจสอบว่าชื่อผู้ใช้ไม่ถูกต้อง เมื่อชื่อผู้ใช้ถูกต้องเราจะเริ่มการตรวจสอบรหัสผ่านซึ่งเป็นกระบวนการที่ค่อนข้างยาว
สำหรับแฮ็กเกอร์มันจะง่ายมากที่จะเข้าใจว่ามีผู้ใช้อยู่หรือไม่
อย่าตอบกลับทันทีเมื่อชื่อผู้ใช้ผิด
ไม่จำเป็นต้องพูด: อย่าให้คำตอบว่าอะไรผิด "ข้อมูลรับรองไม่ถูกต้อง" โดยทั่วไป
@ csharptest.net 's และคริสเตียน Gollhardt ของคำตอบที่ดีขอบคุณมาก แต่หลังจากเรียกใช้รหัสนี้ในการผลิตโดยมีการบันทึกหลายล้านรายการฉันพบว่ามีหน่วยความจำรั่วไหล คลาส RNGCryptoServiceProviderและRfc2898DeriveBytesมาจาก IDisposable แต่เราไม่ได้กำจัดทิ้ง ฉันจะเขียนวิธีแก้ปัญหาของฉันเป็นคำตอบหากมีคนต้องการเวอร์ชันที่จำหน่ายแล้ว
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;
}
}
}
การใช้งาน:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
ใช้คลาสด้านล่างเพื่อสร้างเกลือก่อน ผู้ใช้แต่ละคนจำเป็นต้องมีเกลือที่แตกต่างกันเราสามารถบันทึกไว้ในฐานข้อมูลพร้อมกับคุณสมบัติของผู้ใช้อื่น ๆ ค่ารอบเป็นตัวกำหนดจำนวนครั้งที่จะแฮชรหัสผ่าน
สำหรับรายละเอียดเพิ่มเติม: https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.-ctor?view=netcore-3.1#System_Security_Cryptography_Rfc2898DeriveBytes__ctor_System_Byte___System_Byte___System_Int32 _
public class HashSaltWithRounds
{
int saltLength = 32;
public byte[] GenerateSalt()
{
using (var randomNumberGenerator = new RNGCryptoServiceProvider())
{
var randomNumber = new byte[saltLength];
randomNumberGenerator.GetBytes(randomNumber);
return randomNumber;
}
}
public string HashDataWithRounds(byte[] password, byte[] salt, int rounds)
{
using(var rfc2898= new Rfc2898DeriveBytes(password, salt, rounds))
{
return Convert.ToBase64String(rfc2898.GetBytes(32));
}
}
}
เราสามารถเรียกใช้จากแอปพลิเคชันคอนโซลได้ดังนี้ ฉันได้ถกรหัสผ่านครั้งที่สองโดยใช้เกลือเดียวกัน
public class Program
{
public static void Main(string[] args)
{
int numberOfIterations = 99;
var hashFunction = new HashSaltWithRounds();
string password = "Your Password Here";
byte[] salt = hashFunction.GenerateSalt();
var hashedPassword1 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
var hashedPassword2 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
Console.WriteLine($"hashedPassword1 :{hashedPassword1}");
Console.WriteLine($"hashedPassword2 :{hashedPassword2}");
Console.WriteLine(hashedPassword1.Equals(hashedPassword2));
Console.ReadLine();
}
}
using
คำสั่งหรือเรียกร้องClear()
เมื่อคุณใช้งานเสร็จแล้ว