我想在手机上存储密码的哈希值,但是不确定该如何操作。我只能找到加密方法。如何正确地对密码进行哈希处理?
我想在手机上存储密码的哈希值,但是不确定该如何操作。我只能找到加密方法。如何正确地对密码进行哈希处理?
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);
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
/* 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 core 有兴趣,我也在 Code Review 上有一个 .net core 版本。
V1
和 V2
决定需要哪种验证方法。 - Christian Gollhardtreturn Hash(password, 10000);
,除此之外,到目前为止我还没有遇到任何问题,它已经在生产中使用了约4年 :) 对于较新的.NET Core,我还编写了一个类,位于code review @pettys。 - Christian Gollhardt更新: 此答案已经严重过时。请使用https://dev59.com/_G855IYBdhLWcg3wuG2W#10402129或https://dev59.com/_G855IYBdhLWcg3wuG2W#73125177中的建议。
您可以选择使用
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);
using
语句中或调用Clear()
。 - vcsjonesmd5
对于几乎所有类型的任务都足够好用了。它的漏洞也只针对非常特定的情况,并且几乎需要攻击者对密码学有深入的了解。 - zerkms这里的大多数其他答案是几年前编写的,因此没有利用较新版本的.NET中引入的许多更近期的功能。现在可以通过更简单和更少的样板和噪音来实现相同的事情。我提出的解决方案还通过允许您在未来修改设置(例如迭代计数等)而不会实际破坏旧哈希值来提供额外的鲁棒性(例如,如果您使用接受的答案提出的解决方案,那么就会发生这种情况)。
使用 .NET 6 中引入的新静态Rfc2898DeriveBytes.Pbkdf2()
方法,无需每次实例化和释放对象。
使用 .NET 6 中引入的新RandomNumberGenerator
类及其静态 GetBytes
方法来生成盐值。接受的答案中使用的 RNGCryptoServiceProvider
类已过时is obsolete。
在 Verify
方法中使用 CryptographicOperations.FixedTimeEquals
方法(在 .NET Core 2.1 中引入)比手动比较密钥字节(如接受的答案所做的)更好。此外,这还能消除很多嘈杂的样板代码,同时也避免了 timing attacks。
出于安全考虑,将默认的 SHA-1 算法替换为 SHA-256 算法作为底层算法,因为后者是一种更强大、可靠的算法。
Hash
方法返回的字符串具有以下结构:
[key]:[salt]:[iterations]:[algorithm]
这是此解决方案最重要的优势,它意味着我们基本上在最终字符串中“包括有关用于创建哈希的配置的元数据。这使我们可以在将来更改散列器类中的设置(如迭代次数、盐/密钥大小等)而不会破坏使用旧设置创建的先前哈希。例如,接受的答案没有考虑当前配置值来验证哈希。
Convert.ToHexString
和Convert.FromHexString
更改为Convert.ToBase64
和Convert.FromBase64
即可。其余逻辑保持不变。代码如下:
public static class SecretHasher
{
private const int _saltSize = 16; // 128 bits
private const int _keySize = 32; // 256 bits
private const int _iterations = 50000;
private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;
private const char segmentDelimiter = ':';
public static string Hash(string input)
{
byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
input,
salt,
_iterations,
_algorithm,
_keySize
);
return string.Join(
segmentDelimiter,
Convert.ToHexString(hash),
Convert.ToHexString(salt),
_iterations,
_algorithm
);
}
public static bool Verify(string input, string hashString)
{
string[] segments = hashString.Split(segmentDelimiter);
byte[] hash = Convert.FromHexString(segments[0]);
byte[] salt = Convert.FromHexString(segments[1]);
int iterations = int.Parse(segments[2]);
HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);
byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(
input,
salt,
iterations,
algorithm,
hash.Length
);
return CryptographicOperations.FixedTimeEquals(inputHash, hash);
}
}
使用方法:
// Hash:
string password = "...";
string hashed = SecretHasher.Hash(password);
// Verify:
string enteredPassword = "...";
bool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);
_algorithm
,我已经修复了。
感谢你发现了这个问题。 - aradalvand@csharptest.net和Christian 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);
PasswordHasher<TUser>
。Microsoft.AspNetCore.Identity
Microsoft.Extensions.Identity.Core.dll
(NuGet | Source)
HashPassword()
函数:var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);
要验证密码,使用VerifyHashedPassword()
:
var passwordVerificationResult = new PasswordHasher<object?>().VerifyHashedPassword(null, hashedPassword, password);
switch (passwordVerificationResult)
{
case PasswordVerificationResult.Failed:
Console.WriteLine("Password incorrect.");
break;
case PasswordVerificationResult.Success:
Console.WriteLine("Password ok.");
break;
case PasswordVerificationResult.SuccessRehashNeeded:
Console.WriteLine("Password ok but should be rehashed and updated.");
break;
default:
throw new ArgumentOutOfRangeException();
}
优点:
PasswordHasherOptions
)。缺点:
<TUser>
作为虚拟参数。它是 ASP.NET Core Identity 的一部分,这意味着将来可能会有一些依赖于 Identity 框架。 - Klyuch我使用哈希和盐来进行密码加密(与 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);
}
}
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=
*/
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();
}
}