使用PBKDF2算法来创建哈希的方法。PBKDF2全称Password-Based Key Derivation Function,它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,最终产生秘钥。如果重复的次数足够大,破解的成本将非常大。
PBKDF2定义如下
DK = PBKDF2(PRF, Password, Salt, c, dkLen)
- PRF 是一个伪随机函数,可以简单的理解为 Hash 函数。
- Password 表示口令 。
- Salt 表示盐值,一个随机数。
- c 表示迭代次数。
- dkLen 表示最后输出的密钥长度。
我们先引入NuGet包
Install-Package Microsoft.AspNetCore.Cryptography.KeyDerivation -Version 3.1.6
我们创建一个加密密码的方法,传入需要加密密码,和盐。参数的含义已经写在注释里了。
private static string HashPassword(string value, string salt) { var valueBytes = KeyDerivation.Pbkdf2( password: value,//密码 salt: Encoding.UTF8.GetBytes(salt),//盐 prf: KeyDerivationPrf.HMACSHA512,//伪随机函数,这里是SHA-512 iterationCount: 10000,//迭代次数 numBytesRequested: 256 / 8);//最后输出的秘钥长度 return Convert.ToBase64String(valueBytes); }
现在密码有了,还缺点盐,我们再写一个随机盐生成的方法。
private static string GenerateSalt() { byte[] randomBytes = new byte[128 / 8]; using (var generator = RandomNumberGenerator.Create()) { generator.GetBytes(randomBytes); return Convert.ToBase64String(randomBytes); } }
好了,现在我们有了密码,有了盐,也有了加密好的hash值,那么用户登录的时候,如何校验密码是否正确呢,我们再写一个hash校验的方法。这时我们需要输入用户输入的密码,盐,以及生成好的hash值,就可以判断用户输入的密码是否正确了。
private static bool Validate(string password, string salt, string hash) => HashPassword(password, salt) == hash;
至此,内部的密码加密和校验部分已经完成了,但还有一个问题,就是创建用户密码的时候,如何存储的用户密码的hash值和盐呢,这里我采取使用“点”进行拼接存储,即使用salt.hash的形式,相应的,取值校验,再以“点”进行拆分。具体代码如下
public static string HashPassword(string password) { var salt = GenerateSalt(); var hash = HashPassword(password, salt); var result = $"{salt}.{hash}"; Console.WriteLine("hash result:{0}", result); return result; }
相应的,我们再提供一个对外的密码校验函数
public static bool VerifyHashedPassword(string password, string storePassword) { if (string.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrEmpty(storePassword)) { throw new ArgumentNullException(nameof(storePassword)); } var parts = storePassword.Split('.'); var salt = parts[0]; var hash = parts[1]; return Validate(password, salt, hash); ; }
至此,所有代码编写完成,我们下面来试试效果。
我们输入一个简单的密码,1234,循环了5次,模拟5个用户都使用1234这个密码,我们看一下效果。
var password = "1234"; for (int i = 0; i < 5; i++) { var result = PasswordHasher.HashPassword(password); var right = PasswordHasher.VerifyHashedPassword(password, result); Console.WriteLine("password is right:{0}", right); }
可以看出这五条数据虽然拥有相同的密码,但是拥有不同的盐和hash值。
-
完整的代码如下
-
public class PasswordHasher { private static string HashPassword(string value, string salt) { var valueBytes = KeyDerivation.Pbkdf2( password: value, salt: Encoding.UTF8.GetBytes(salt), prf: KeyDerivationPrf.HMACSHA512, iterationCount: 10000, numBytesRequested: 256 / 8); return Convert.ToBase64String(valueBytes); } public static string HashPassword(string password) { var salt = GenerateSalt(); var hash = HashPassword(password, salt); var result = $"{salt}.{hash}"; Console.WriteLine("hash result:{0}", result); return result; } private static bool Validate(string password, string salt, string hash) => HashPassword(password, salt) == hash; public static bool VerifyHashedPassword(string password, string storePassword) { if (string.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrEmpty(storePassword)) { throw new ArgumentNullException(nameof(storePassword)); } var parts = storePassword.Split('.'); var salt = parts[0]; var hash = parts[1]; return Validate(password, salt, hash); ; } private static string GenerateSalt() { byte[] randomBytes = new byte[128 / 8]; using (var generator = RandomNumberGenerator.Create()) { generator.GetBytes(randomBytes); return Convert.ToBase64String(randomBytes); } } }