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

net des 加密 .net密码加密_net des 加密

可以看出这五条数据虽然拥有相同的密码,但是拥有不同的盐和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);
            }
        }

    }