C# 中的密码哈希和给密码加盐

我刚刚浏览了 DavidHayden 的这篇文章 对用户密码进行哈希处理

我真的不明白他想要达到的目的。

下面是他的代码:

private static string CreateSalt(int size)
{
//Generate a cryptographic random number.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[size];
rng.GetBytes(buff);


// Return a Base64 string representation of the random number.
return Convert.ToBase64String(buff);
}


private static string CreatePasswordHash(string pwd, string salt)
{
string saltAndPwd = String.Concat(pwd, salt);
string hashedPwd =
FormsAuthentication.HashPasswordForStoringInConfigFile(
saltAndPwd, "sha1");
return hashedPwd;
}

有没有其他的c#方法来散列密码并添加盐?

279194 次浏览

Salt用于为哈希添加额外的复杂性,使其更难进行暴力破解。

来自文章在Sitepoint:

黑客仍然可以执行 这就是所谓的字典攻击。 恶意方可能制造 字典攻击采取,为 例如,10万个密码 知道人们经常使用(例如城市 名字,运动队等),散列它们, 然后比较每一项 中的每一行 数据库表中。如果黑客找到一个 匹配,宾果!他们有你的密码。 然而,为了解决这个问题,我们

要给哈希加盐,我们简单地提出 一串看似随机的文本, 把它和密码连接起来 由用户提供,然后对两者进行哈希 随机生成的字符串和 密码一起作为一个值。我们 然后把哈希和盐都留着 作为Users中的独立字段 表。< / p >

在这种情况下,不仅a 黑客需要猜密码, 他们还得猜盐。 在清晰的文本中添加盐可以改善效果 安全:现在,如果黑客试图 字典攻击,他必须哈希他的 10万份加盐的 用户行。尽管它仍然 黑客入侵的可能性

在. net中没有自动执行此操作的方法,因此您必须使用上面的解决方案。

实际上,这有点奇怪,字符串转换-成员资格提供程序将它们放入配置文件。哈希和盐是二进制blobs,你不需要把它们转换成字符串,除非你想把它们放在文本文件中。

在我的书,开始ASP。网络安全,(哦,终于,一个借口来皮条书)我做以下

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
HashAlgorithm algorithm = new SHA256Managed();


byte[] plainTextWithSaltBytes =
new byte[plainText.Length + salt.Length];


for (int i = 0; i < plainText.Length; i++)
{
plainTextWithSaltBytes[i] = plainText[i];
}
for (int i = 0; i < salt.Length; i++)
{
plainTextWithSaltBytes[plainText.Length + i] = salt[i];
}


return algorithm.ComputeHash(plainTextWithSaltBytes);
}

问题中以盐代为例。可以使用Encoding.UTF8.GetBytes(string)将文本转换为字节数组。如果你必须将一个散列转换为字符串表示形式,你可以使用Convert.ToBase64StringConvert.FromBase64String将其转换回来。

你应该注意,你不能在字节数组上使用相等操作符,它检查引用,所以你应该简单地在两个数组中循环检查每个字节

public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
if (array1.Length != array2.Length)
{
return false;
}


for (int i = 0; i < array1.Length; i++)
{
if (array1[i] != array2[i])
{
return false;
}
}


return true;
}

总是使用一个新的盐每个密码。盐不必保密,可以与哈希本身一起存储。

跟吹镖说的一样,但用了更少的代码。使用Linq或CopyTo来连接数组。

public static byte[] Hash(string value, byte[] salt)
{
return Hash(Encoding.UTF8.GetBytes(value), salt);
}


public static byte[] Hash(byte[] value, byte[] salt)
{
byte[] saltedValue = value.Concat(salt).ToArray();
// Alternatively use CopyTo.
//var saltedValue = new byte[value.Length + salt.Length];
//value.CopyTo(saltedValue, 0);
//salt.CopyTo(saltedValue, value.Length);


return new SHA256Managed().ComputeHash(saltedValue);
}

Linq也有一个比较字节数组的简单方法。

public bool ConfirmPassword(string password)
{
byte[] passwordHash = Hash(password, _passwordSalt);


return _passwordHash.SequenceEqual(passwordHash);
}

然而,在实现任何这些之前,检查这篇文章。对于密码哈希,你可能需要一个慢的哈希算法,而不是一个快的。

为此,有一个很慢的Rfc2898DeriveBytes类(并且可以变得更慢),它可以回答原始问题的第二部分,因为它可以接受密码和盐并返回散列。更多信息见这个问题。注意,Stack Exchange正在使用Rfc2898DeriveBytes用于密码哈希(源代码在这里)。

我一直在读,像SHA256这样的哈希函数并不是真正用于存储密码的: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes < / p >

相反,自适应密钥派生函数,如PBKDF2, bcrypt或scrypt。下面是微软在他们的Microsoft. aspnet . identity库中为PasswordHasher编写的基于PBKDF2的代码:

/* =======================
* HASHED PASSWORD FORMATS
* =======================
*
* Version 3:
* PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
* Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
* (All UInt32s are stored big-endian.)
*/


public string HashPassword(string password)
{
var prf = KeyDerivationPrf.HMACSHA256;
var rng = RandomNumberGenerator.Create();
const int iterCount = 10000;
const int saltSize = 128 / 8;
const int numBytesRequested = 256 / 8;


// Produce a version 3 (see comment above) text hash.
var salt = new byte[saltSize];
rng.GetBytes(salt);
var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);


var outputBytes = new byte[13 + salt.Length + subkey.Length];
outputBytes[0] = 0x01; // format marker
WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
WriteNetworkByteOrder(outputBytes, 5, iterCount);
WriteNetworkByteOrder(outputBytes, 9, saltSize);
Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
return Convert.ToBase64String(outputBytes);
}


public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
var decodedHashedPassword = Convert.FromBase64String(hashedPassword);


// Wrong version
if (decodedHashedPassword[0] != 0x01)
return false;


// Read header information
var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);


// Read the salt: must be >= 128 bits
if (saltLength < 128 / 8)
{
return false;
}
var salt = new byte[saltLength];
Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);


// Read the subkey (the rest of the payload): must be >= 128 bits
var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
if (subkeyLength < 128 / 8)
{
return false;
}
var expectedSubkey = new byte[subkeyLength];
Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);


// Hash the incoming password and verify it
var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
return actualSubkey.SequenceEqual(expectedSubkey);
}


private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
buffer[offset + 0] = (byte)(value >> 24);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 3] = (byte)(value >> 0);
}


private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
return ((uint)(buffer[offset + 0]) << 24)
| ((uint)(buffer[offset + 1]) << 16)
| ((uint)(buffer[offset + 2]) << 8)
| ((uint)(buffer[offset + 3]));
}

注意,这需要安装Microsoft.AspNetCore.Cryptography.KeyDerivation nuget包,需要. net标准2.0(。NET 4.6.1或更高版本)。关于。net的早期版本,请参阅微软System.Web.Helpers库中的加密类。

< p > 2015年11月更新
更新答案,使用来自不同微软库的实现,该库使用PBKDF2-HMAC-SHA256哈希,而不是PBKDF2-HMAC-SHA1(注意,如果iterCount足够高,PBKDF2-HMAC-SHA1为还是安全的)。你可以查看复制简化代码的,因为它实际上处理验证和升级从上一个答案实现的哈希值,如果你将来需要增加iterCount,这很有用

呸,这个好多了!http://sourceforge.net/projects/pwdtknet/和它是更好的,因为.....它执行关键的拉伸并使用HMACSHA512:)

我就是这么做的。我使用ProtectedData api创建散列并存储它:

    public static string GenerateKeyHash(string Password)
{
if (string.IsNullOrEmpty(Password)) return null;
if (Password.Length < 1) return null;


byte[] salt = new byte[20];
byte[] key = new byte[20];
byte[] ret = new byte[40];


try
{
using (RNGCryptoServiceProvider randomBytes = new RNGCryptoServiceProvider())
{
randomBytes.GetBytes(salt);


using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
{
key = hashBytes.GetBytes(20);
Buffer.BlockCopy(salt, 0, ret, 0, 20);
Buffer.BlockCopy(key, 0, ret, 20, 20);
}
}
// returns salt/key pair
return Convert.ToBase64String(ret);
}
finally
{
if (salt != null)
Array.Clear(salt, 0, salt.Length);
if (key != null)
Array.Clear(key, 0, key.Length);
if (ret != null)
Array.Clear(ret, 0, ret.Length);
}
}


public static bool ComparePasswords(string PasswordHash, string Password)
{
if (string.IsNullOrEmpty(PasswordHash) || string.IsNullOrEmpty(Password)) return false;
if (PasswordHash.Length < 40 || Password.Length < 1) return false;


byte[] salt = new byte[20];
byte[] key = new byte[20];
byte[] hash = Convert.FromBase64String(PasswordHash);


try
{
Buffer.BlockCopy(hash, 0, salt, 0, 20);
Buffer.BlockCopy(hash, 20, key, 0, 20);


using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
{
byte[] newKey = hashBytes.GetBytes(20);


if (newKey != null)
if (newKey.SequenceEqual(key))
return true;
}
return false;
}
finally
{
if (salt != null)
Array.Clear(salt, 0, salt.Length);
if (key != null)
Array.Clear(key, 0, key.Length);
if (hash != null)
Array.Clear(hash, 0, hash.Length);
}
}


public static byte[] DecryptData(string Data, byte[] Salt)
{
if (string.IsNullOrEmpty(Data)) return null;


byte[] btData = Convert.FromBase64String(Data);


try
{
return ProtectedData.Unprotect(btData, Salt, DataProtectionScope.CurrentUser);
}
finally
{
if (btData != null)
Array.Clear(btData, 0, btData.Length);
}
}


public static string EncryptData(byte[] Data, byte[] Salt)
{
if (Data == null) return null;
if (Data.Length < 1) return null;


byte[] buffer = new byte[Data.Length];


try
{
Buffer.BlockCopy(Data, 0, buffer, 0, Data.Length);
return System.Convert.ToBase64String(ProtectedData.Protect(buffer, Salt, DataProtectionScope.CurrentUser));
}
finally
{
if (buffer != null)
Array.Clear(buffer, 0, buffer.Length);
}
}
create proc [dbo].[hash_pass] @family nvarchar(50), @username nvarchar(50), @pass nvarchar(Max),``` @semat nvarchar(50), @tell nvarchar(50)


as insert into tbl_karbar values (@family,@username,(select HASHBYTES('SHA1' ,@pass)),@semat,@tell)

在回答这一部分的原始问题“是否有任何其他c#方法来哈希密码”,你可以使用ASP。NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity.EntityFramework/3.0.0-rc1-final

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using System.Security.Principal;


namespace HashTest{




class Program
{
static void Main(string[] args)
{


WindowsIdentity wi = WindowsIdentity.GetCurrent();


var ph = new PasswordHasher<WindowsIdentity>();


Console.WriteLine(ph.HashPassword(wi,"test"));


Console.WriteLine(ph.VerifyHashedPassword(wi,"AQAAAAEAACcQAAAAEA5S5X7dmbx/NzTk6ixCX+bi8zbKqBUjBhID3Dg1teh+TRZMkAy3CZC5yIfbLqwk2A==","test"));


}
}




}

我已经创建了一个库SimpleHashing。网,以便使用微软提供的基本类简化哈希过程。普通的SHA已经不足以安全地存储密码了。

库使用Bcrypt的哈希格式,但由于没有官方的MS实现,我更喜欢使用框架中可用的东西(即PBKDF2),但它有点难以开盒。

这是一个如何使用库的快速示例:

ISimpleHash simpleHash = new SimpleHash();


// Creating a user hash, hashedPassword can be stored in a database
// hashedPassword contains the number of iterations and salt inside it similar to bcrypt format
string hashedPassword = simpleHash.Compute("Password123");


// Validating user's password by first loading it from database by username
string storedHash = _repository.GetUserPasswordHash(username);
isPasswordValid = simpleHash.Verify("Password123", storedHash);
 protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e)
{
string salt =createSalt(10);
string hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt);
m_SaltHash_TextBox.Text=Salt;
m_SaltSHA256Hash_TextBox.Text=hashedPassword;


}
public string createSalt(int size)
{
var rng= new System.Security.Cyptography.RNGCyptoServiceProvider();
var buff= new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}




public string GenerateSHA256Hash(string input,string salt)
{
byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt);
new System.Security.Cyptography.SHA256Managed();
byte[]hash=sha256hashString.ComputedHash(bytes);
return bytesArrayToHexString(hash);
}

我阅读了所有的答案,我认为这些足够了,特别是@Michael文章与慢哈希和@CodesInChaos好的评论,但我决定分享我的代码片段哈希/验证可能是有用的,它不需要[Microsoft.AspNet.Cryptography.KeyDerivation]。

    private static bool SlowEquals(byte[] a, byte[] b)
{
uint diff = (uint)a.Length ^ (uint)b.Length;
for (int i = 0; i < a.Length && i < b.Length; i++)
diff |= (uint)(a[i] ^ b[i]);
return diff == 0;
}


private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
{
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
pbkdf2.IterationCount = iterations;
return pbkdf2.GetBytes(outputBytes);
}


private static string CreateHash(string value, int salt_bytes, int hash_bytes, int pbkdf2_iterations)
{
// Generate a random salt
RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
byte[] salt = new byte[salt_bytes];
csprng.GetBytes(salt);


// Hash the value and encode the parameters
byte[] hash = PBKDF2(value, salt, pbkdf2_iterations, hash_bytes);


//You need to return the salt value too for the validation process
return Convert.ToBase64String(hash) + ":" +
Convert.ToBase64String(hash);
}


private static bool ValidateHash(string pureVal, string saltVal, string hashVal, int pbkdf2_iterations)
{
try
{
byte[] salt = Convert.FromBase64String(saltVal);
byte[] hash = Convert.FromBase64String(hashVal);


byte[] testHash = PBKDF2(pureVal, salt, pbkdf2_iterations, hash.Length);
return SlowEquals(hash, testHash);
}
catch (Exception ex)
{
return false;
}
}

请注意慢工功能,这是如此重要,最后,我希望这有助于和请不要犹豫,建议我更好的方法。

我创建了一个类,它有以下方法:

  1. < p >创建盐

  2. < p >散列输入

  3. < p >验证输入

    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);
    }
    }
    

使用微软的System.Web.Helpers.Crypto NuGet包。它会自动向哈希中添加盐。

您可以像这样散列密码:var hash = Crypto.HashPassword("foo");

你像这样验证一个密码:var verified = Crypto.VerifyHashedPassword(hash, "foo");

如果你不使用asp.net或。net core,在>= .net Standard 2.0项目中也有一个简单的方法。

首先,你可以设置所需的哈希大小,盐和迭代次数,这与哈希生成的持续时间有关:

private const int SaltSize = 32;
private const int HashSize = 32;
private const int IterationCount = 10000;

为了生成密码哈希和盐,你可以使用这样的东西:

public static string GeneratePasswordHash(string password, out string salt)
{
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
{
rfc2898DeriveBytes.IterationCount = IterationCount;
byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
byte[] saltData = rfc2898DeriveBytes.Salt;
salt = Convert.ToBase64String(saltData);
return Convert.ToBase64String(hashData);
}
}

要验证用户输入的密码是否有效,您可以检查数据库中的值:

public static bool VerifyPassword(string password, string passwordHash, string salt)
{
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
{
rfc2898DeriveBytes.IterationCount = IterationCount;
rfc2898DeriveBytes.Salt = Convert.FromBase64String(salt);
byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
return Convert.ToBase64String(hashData) == passwordHash;
}
}

下面的单元测试展示了使用方法:

string password = "MySecret";


string passwordHash = PasswordHasher.GeneratePasswordHash(password, out string salt);


Assert.True(PasswordHasher.VerifyPassword(password, passwordHash, salt));
Assert.False(PasswordHasher.VerifyPassword(password.ToUpper(), passwordHash, salt));

Microsoft Rfc2898DeriveBytes Source .

对于最初的问题已经有了很好的答案,但我想补充的是,“SequenceEqual"使定时攻击成为可能。

检查序列(字节)是否相同的正常方法是将顺序中的每个字节与第二个字节进行比较。第一个是错误的,停止比较和“错误”;返回。

byte[] hash1 = ...
byte[] hash2 = ...
// can be exploited with a timing attack
bool equals = hash1.SequenceEqual(hash2);

因此,攻击者需要256个字符串,每个可能的起始字节。他根据机制运行每个字符串,得到结果所需时间最长的字符串是具有正确的第一个字节的字符串。然后可以在下一个字节上以类似的方式继续攻击…等等。

我发现在这里是一个更好的方法,有一个很好的解释。

[MethodImpl(MethodImplOptions.NoOptimization)]
private static bool slowEquals(byte[] a, byte[] b)
{
int diff = a.Length ^ b.Length;


for (int i = 0; i < a.Length && i < b.Length; i++)
diff |= a[i] ^ b[i];


return diff == 0;
}

我在。netcore6中使用这个

public class Encryption
{
public string CreateSalt(int size)
{
//Generate a cryptographic random number.
byte[] buff = new byte[size];
RandomNumberGenerator rng = RandomNumberGenerator.Create();
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}


public string GenerateHash(string input, string salt)
{
byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
SHA256 sha = SHA256.Create();
byte[] hash = sha.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}


public bool Equals(string plainTextInput, string hashedInput, string salt)
{
string newHashedPin = GenerateHash(plainTextInput, salt);
return newHashedPin.Equals(hashedInput);
}
}

2022(。NET 6+)解决方案:

这里的大多数其他答案(包括接受的答案)都使用SHA-256哈希算法,即不再适合存储用户密码了,即使你使用盐。相反,您应该选择较慢的哈希函数,例如Bcrypt, Argon2, Scrypt或PBKDF2;后者是. net中唯一本地可用的。

你可以在另一个问题中找到帮助方法和诸如此类的方法来创建PBKDF2散列,但我下面提供的方法比那个问题中提供的方法甚至比这里的一些方法(如这一个)有以下优点。

优点:

  • 使用。net 6中引入的新的静态Rfc2898DeriveBytes.Pbkdf2()方法,消除了每次实例化和释放对象的需要。

  • 使用新的RandomNumberGenerator类及其静态GetBytes方法(在. net 6中引入)来生成盐。在最初的问题和这里的许多答案中使用的RNGCryptoServiceProvider类。

  • 使用CryptographicOperations.FixedTimeEquals方法(在. net Core 2.1中引入)来比较Verify方法中的关键字节,而不是像接受的答案那样手工进行比较。这样做,除了删除大量嘈杂的样板之外,还会使计时攻击无效。

  • 使用SHA-256而不是默认的SHA-1作为底层算法,只是为了安全起见,因为后者是一个更健壮和可靠的算法。

  • Hash方法返回的字符串(扩展为Verify方法接收的字符串)具有以下结构:

    [key]:[salt]:[iterations]:[algorithm]

    这是这个特殊解决方案最重要的优势;这是我遇到的大多数其他解决方案(使用PBKDF2)倾向于忽视的东西,实际上没有考虑到,尽管它是至关重要的。相反,它们通常依赖当前的配置值来验证哈希值,这意味着一旦你决定更改任何配置值,任何先前创建的哈希值将不再被正确验证。

其他的点:

  • 我在返回的哈希字符串中使用键和盐的十六进制表示。如果你愿意,你可以使用base64,只需要将每次出现的Convert.ToHexStringConvert.FromHexString分别更改为Convert.ToBase64Convert.FromBase64。其余的逻辑保持完全相同。
  • 通常推荐的盐的大小是64位或以上。我把它设置为128位。
  • 键大小通常应该与所选算法的自然输出大小相同-参见这样的评论。在我们的例子中,正如我前面提到的,底层算法是SHA-256,它的输出大小是256位,这正是我们设置的密钥大小。
  • 如果你计划使用它来存储用户密码,通常建议使用至少 10,000次迭代或更多。我将默认值设置为50,000,当然您可以根据自己的需要进行更改。

代码:

public static class SecretHasher2
{
private const int _saltSize = 16; // 128 bits
private const int _keySize = 32; // 256 bits
private const int _iterations = 100000;
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);