.NET6中哈希算法的简化用法的实现

时间:2022-06-05 13:59:28

Intro

微软在 .NET 6 中引入一些更简单的 API 来使用 HMAC 哈希算法(MD5/SHA1/SHA256/SHA384/SHA512)

微软的叫法叫做 HMAC One-Shoot method, HMAC 算法在普通的哈希算法基础上增加了一个 key,通过 key 提升了安全性,能够有效避免密码泄露被彩虹表反推出真实密码, JWT(Json Web Token) 除了可以使用 RSA 方式外也支持使用 HMAC 。

New API

新增的 API 定义如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace System.Security.Cryptography {
    public partial class HMACMD5 {
        public static byte[] HashData(byte[] key, byte[] source);
        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
    }
 
    public partial class HMACSHA1 {
        public static byte[] HashData(byte[] key, byte[] source);
        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
    }
 
    public partial class HMACSHA256 {
        public static byte[] HashData(byte[] key, byte[] source);
        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
    }
 
    public partial class HMACSHA384 {
        public static byte[] HashData(byte[] key, byte[] source);
        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
    }
 
    public partial class HMACSHA512 {
        public static byte[] HashData(byte[] key, byte[] source);
        public static byte[] HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
        public static int HashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
        public static bool TryHashData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
    }
}

Sample Before

在之前的版本中想要实现计算 HMAC 算法会比较复杂,之前实现了一个 HashHelper 来封装了常用的 Hash 算法和 HMAC 算法,HashHelper 部分代码如下,完整代码可以从 Github 获取:https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Helpers/HashHelper.cs

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/// <summary>
/// 获取哈希之后的字符串
/// </summary>
/// <param name="type">哈希类型</param>
/// <param name="source">源</param>
/// <param name="key">key</param>
/// <param name="isLower">是否是小写</param>
/// <returns>哈希算法处理之后的字符串</returns>
public static string GetHashedString(HashType type, byte[] source, byte[]? key, bool isLower = false)
{
    Guard.NotNull(source, nameof(source));
    if (source.Length == 0)
    {
        return string.Empty;
    }
    var hashedBytes = GetHashedBytes(type, source, key);
    var sbText = new StringBuilder();
    if (isLower)
    {
        foreach (var b in hashedBytes)
        {
            sbText.Append(b.ToString("x2"));
        }
    }
    else
    {
        foreach (var b in hashedBytes)
        {
            sbText.Append(b.ToString("X2"));
        }
    }
    return sbText.ToString();
}
 
/// <summary>
/// 计算字符串Hash值
/// </summary>
/// <param name="type">hash类型</param>
/// <param name="str">要hash的字符串</param>
/// <returns>hash过的字节数组</returns>
public static byte[] GetHashedBytes(HashType type, string str) => GetHashedBytes(type, str, Encoding.UTF8);
 
/// <summary>
/// 计算字符串Hash值
/// </summary>
/// <param name="type">hash类型</param>
/// <param name="str">要hash的字符串</param>
/// <param name="encoding">编码类型</param>
/// <returns>hash过的字节数组</returns>
public static byte[] GetHashedBytes(HashType type, string str, Encoding encoding)
{
    Guard.NotNull(str, nameof(str));
    if (str == string.Empty)
    {
        return Array.Empty<byte>();
    }
    var bytes = encoding.GetBytes(str);
    return GetHashedBytes(type, bytes);
}
 
/// <summary>
/// 获取Hash后的字节数组
/// </summary>
/// <param name="type">哈希类型</param>
/// <param name="bytes">原字节数组</param>
/// <returns></returns>
public static byte[] GetHashedBytes(HashType type, byte[] bytes) => GetHashedBytes(type, bytes, null);
 
/// <summary>
/// 获取Hash后的字节数组
/// </summary>
/// <param name="type">哈希类型</param>
/// <param name="key">key</param>
/// <param name="bytes">原字节数组</param>
/// <returns></returns>
public static byte[] GetHashedBytes(HashType type, byte[] bytes, byte[]? key)
{
    Guard.NotNull(bytes, nameof(bytes));
    if (bytes.Length == 0)
    {
        return bytes;
    }
 
    HashAlgorithm algorithm = null!;
    try
    {
        if (key == null)
        {
            algorithm = type switch
            {
                    HashType.SHA1 => new SHA1Managed(),
                    HashType.SHA256 => new SHA256Managed(),
                    HashType.SHA384 => new SHA384Managed(),
                    HashType.SHA512 => new SHA512Managed(),
                    _ => MD5.Create()
            };
        }
        else
        {
            algorithm = type switch
            {
                    HashType.SHA1 => new HMACSHA1(key),
                    HashType.SHA256 => new HMACSHA256(key),
                    HashType.SHA384 => new HMACSHA384(key),
                    HashType.SHA512 => new HMACSHA512(key),
                    _ => new HMACMD5(key)
            };
        }
        return algorithm.ComputeHash(bytes);
    }
    finally
    {
        algorithm.Dispose();
    }
}

使用示例如下:

?
1
2
3
4
5
6
7
8
9
HashHelper.GetHashedBytes(HashType.MD5, "test");
HashHelper.GetHashedBytes(HashType.MD5, "test".GetBytes());
HashHelper.GetHashedBytes(HashType.MD5, "test", "testKey");
HashHelper.GetHashedBytes(HashType.MD5, "test".GetBytes(), "testKey".GetBytes());
 
HashHelper.GetHashedString(HashType.MD5, "test");
HashHelper.GetHashedString(HashType.SHA1, "test".GetBytes());
HashHelper.GetHashedString(HashType.SHA256, "test", "testKey");
HashHelper.GetHashedString(HashType.MD5, "test".GetBytes(), "testKey".GetBytes());

New API Sample

有了新的 API 以后可以怎么简化呢,来看下面的示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var bytes = "test".GetBytes();
var keyBytes = "test-key".GetBytes();
 
// HMACMD5
var hmd5V1 = HMACMD5.HashData(keyBytes, bytes);
var hmd5V2 = HashHelper.GetHashedBytes(HashType.MD5, bytes, keyBytes);
Console.WriteLine(hmd5V2.SequenceEqual(hmd5V1));
 
// HMACSHA1
var hsha1V1 = HMACSHA1.HashData(keyBytes, bytes);
var hsha1V2 = HashHelper.GetHashedBytes(HashType.SHA1, bytes, keyBytes);
Console.WriteLine(hsha1V2.SequenceEqual(hsha1V1));
 
// HMACSHA256
var hsha256V1 = HMACSHA256.HashData(keyBytes, bytes);
var hsha256V2 = HashHelper.GetHashedBytes(HashType.SHA256, bytes, keyBytes);
Console.WriteLine(hsha256V2.SequenceEqual(hsha256V1));
 
// HMACSHA384
var hsha384V1 = HMACSHA384.HashData(keyBytes ,bytes);
var hsha384V2 = HashHelper.GetHashedBytes(HashType.SHA384, bytes, keyBytes);
Console.WriteLine(hsha384V2.SequenceEqual(hsha384V1));
 
// HMACSHA512
var hsha512V1 = HMACSHA512.HashData(keyBytes ,bytes);
var hsha512V2 = HashHelper.GetHashedBytes(HashType.SHA512, bytes, keyBytes);
Console.WriteLine(hsha512V2.SequenceEqual(hsha512V1));

直接使用对应的 HMAC 哈希算法的 HashData 方法即可,传入对应的 key 和 原始内容就可以了,上面是和我们 HashHelper 封装的方法进行对比,看结果是否一致,都是一致的,输出结果如下:

.NET6中哈希算法的简化用法的实现

More

对于普通的哈希算法,微软其实在 .NET 5 就已经支持了上面的用法,可以尝试一下下面的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var bytes = "test".GetBytes();
 
// MD5
var md5V1 = MD5.HashData(bytes);
var md5V2 = HashHelper.GetHashedBytes(HashType.MD5, bytes);
Console.WriteLine(md5V2.SequenceEqual(md5V1));
 
// SHA1
var sha1V1 = SHA1.HashData(bytes);
var sha1V2 = HashHelper.GetHashedBytes(HashType.SHA1, bytes);
Console.WriteLine(sha1V2.SequenceEqual(sha1V1));
 
// SHA256
var sha256V1 = SHA256.HashData(bytes);
var sha256V2 = HashHelper.GetHashedBytes(HashType.SHA256, bytes);
Console.WriteLine(sha256V2.SequenceEqual(sha256V1));
 
// SHA384
var sha384V1 = SHA384.HashData(bytes);
var sha384V2 = HashHelper.GetHashedBytes(HashType.SHA384, bytes);
Console.WriteLine(sha384V2.SequenceEqual(sha384V1));
 
// SHA512
var sha512V1 = SHA512.HashData(bytes);
var sha512V2 = HashHelper.GetHashedBytes(HashType.SHA512, bytes);
Console.WriteLine(sha512V2.SequenceEqual(sha512V1));

很多时候我们可能都会要使用 MD5 或者 SHA1 之后的字符串,不知道为什么微软没有直接获取一个字符串的方法,如果有这样一个方法,就会更方便了,相比之后,感觉还是自己封装的 HashHelper 使用起来更舒服一些,哈哈,这样的静态方法不够抽象如果要动态替换哈希算法代码可能就有点...

References

https://github.com/dotnet/runtime/pull/53487
https://github.com/dotnet/runtime/issues/40012
https://github.com/dotnet/core/issues/6569#issuecomment-913876347
https://baike.baidu.com/item/hmac/7307543?fr=aladdin
https://github.com/WeihanLi/SamplesInPractice/blob/master/net6sample/HashSample/Program.cs
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Helpers/HashHelper.cs

到此这篇关于.NET 6中哈希算法的简化用法的实现的文章就介绍到这了,更多相关.NET 6 哈希算法内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://developer.51cto.com/art/202109/681193.htm