csh*_*net 275
这里的大多数其他答案都与今天的最佳实践有些过时.因此,这里是使用PBKDF2/Rfc2898DeriveBytes来存储和验证密码的应用.以下代码在本文中的独立类中:另一个如何存储salted密码哈希的示例.基础知识非常简单,所以在这里分解:
步骤1使用加密PRNG创建salt值:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
Run Code Online (Sandbox Code Playgroud)
步骤2创建Rfc2898DeriveBytes并获取哈希值:
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] hash = pbkdf2.GetBytes(20);
Run Code Online (Sandbox Code Playgroud)
步骤3将salt和password字节组合起来供以后使用:
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
Run Code Online (Sandbox Code Playgroud)
步骤4将组合的salt + hash转换为字符串进行存储
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
Run Code Online (Sandbox Code Playgroud)
步骤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, 10000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
if (hashBytes[i+16] != hash[i])
throw new UnauthorizedAccessException();
Run Code Online (Sandbox Code Playgroud)
注意:根据特定应用程序的性能要求,可以减少值"10000".最小值应该在1000左右.
Chr*_*rdt 61
根据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;
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Run Code Online (Sandbox Code Playgroud)
示例哈希可以是这样的:
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我还将哈希中的迭代包含在内以便于使用,如果我们需要升级,还可以升级它.
zer*_*kms 60
更新:这个答案严重过时了.请使用/sf/answers/728149061/中的建议.
你可以使用
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
Run Code Online (Sandbox Code Playgroud)
要么
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
Run Code Online (Sandbox Code Playgroud)
要获得data
可以使用的字节数组
var data = Encoding.ASCII.GetBytes(password);
Run Code Online (Sandbox Code Playgroud)
并从md5data
或返回字符串sha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
Run Code Online (Sandbox Code Playgroud)
Ami*_*adi 35
这里的大多数其他答案都是几年前写的,因此没有利用新版本 .NET 中引入的许多最新功能。现在可以更简单地实现同样的事情,并且样板文件和噪音也少得多。我提出的解决方案还提供了额外的鲁棒性,允许您将来修改设置(例如迭代计数等),而不会实际破坏旧的哈希值(如果您使用接受的答案建议的情况会发生这种情况)解决方案,例如)。
\n使用 .NET 6 中引入的新静态Rfc2898DeriveBytes.Pbkdf2()
方法,无需每次都实例化和处置对象。
使用.NET 6 \xe2\x80\x94 中引入的新RandomNumberGenerator
类及其静态方法 \xe2\x80\x94 来生成盐。接受的答案中使用的类GetBytes
已过时。RNGCryptoServiceProvider
使用CryptographicOperations.FixedTimeEquals
方法(在 .NET Core 2.1 中引入)来比较方法中的关键字节Verify
,而不是像已接受的答案那样手动进行比较 \xe2\x80\x94 。这除了消除大量嘈杂的样板之外,还消除了定时攻击。
使用 SHA-256 而不是默认的 SHA-1 作为底层算法,只是为了安全起见,因为后者是一种更强大、更可靠的算法。
\n该Hash
方法返回的字符串具有以下结构:
[key]:[salt]:[iterations]:[algorithm]
这是该解决方案最重要的优点,这意味着我们基本上包含有关用于在最终字符串中创建哈希的配置的元数据。这允许我们将来更改哈希器类中的设置(例如迭代次数、salt/key 大小等),而不会破坏使用旧设置创建的先前哈希值。例如,这是公认的答案没有考虑到的问题,因为它依赖“当前”配置值来验证哈希值。
\nConvert.ToHexString
和分别更改Convert.FromHexString
为Convert.ToBase64
和Convert.FromBase64
即可。其余逻辑保持完全相同。代码:
\npublic static class SecretHasher\n{\n private const int _saltSize = 16; // 128 bits\n private const int _keySize = 32; // 256 bits\n private const int _iterations = 50000;\n private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;\n\n private const char segmentDelimiter = \':\';\n\n public static string Hash(string input)\n {\n byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);\n byte[] hash = Rfc2898DeriveBytes.Pbkdf2(\n input,\n salt,\n _iterations,\n _algorithm,\n _keySize\n );\n return string.Join(\n segmentDelimiter,\n Convert.ToHexString(hash),\n Convert.ToHexString(salt),\n _iterations,\n _algorithm\n );\n }\n\n public static bool Verify(string input, string hashString)\n {\n string[] segments = hashString.Split(segmentDelimiter);\n byte[] hash = Convert.FromHexString(segments[0]);\n byte[] salt = Convert.FromHexString(segments[1]);\n int iterations = int.Parse(segments[2]);\n HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);\n byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(\n input,\n salt,\n iterations,\n algorithm,\n hash.Length\n );\n return CryptographicOperations.FixedTimeEquals(inputHash, hash);\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n用法:
\n// Hash:\nstring password = "...";\nstring hashed = SecretHasher.Hash(password);\n\n// Verify:\nstring enteredPassword = "...";\nbool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);\n
Run Code Online (Sandbox Code Playgroud)\n
Mar*_*tin 14
我使用哈希和盐进行密码加密(它与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);
}
Run Code Online (Sandbox Code Playgroud)
ibr*_*gon 14
@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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Run Code Online (Sandbox Code Playgroud)
Pan*_*ang 12
在 ASP.NET Core 中,使用PasswordHasher<TUser>
.
•命名空间:Microsoft.AspNetCore.Identity
•大会:Microsoft.Extensions.Identity.Core.dll
(的NuGet |来源)
要散列密码,请使用HashPassword()
:
var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);
Run Code Online (Sandbox Code Playgroud)
要验证密码,请使用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();
}
Run Code Online (Sandbox Code Playgroud)
优点:
PasswordHasherOptions
)。缺点:
归档时间: |
|
查看次数: |
127133 次 |
最近记录: |