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 定义如下:
- namespaceSystem.Security.Cryptography{
- publicpartialclassHMACMD5{
- publicstaticbyte[]HashData(byte[]key,byte[]source);
- publicstaticbyte[]HashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source);
- publicstaticintHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination);
- publicstaticboolTryHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination,outintbytesWritten);
- }
- publicpartialclassHMACSHA1{
- publicstaticbyte[]HashData(byte[]key,byte[]source);
- publicstaticbyte[]HashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source);
- publicstaticintHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination);
- publicstaticboolTryHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination,outintbytesWritten);
- }
- publicpartialclassHMACSHA256{
- publicstaticbyte[]HashData(byte[]key,byte[]source);
- publicstaticbyte[]HashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source);
- publicstaticintHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination);
- publicstaticboolTryHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination,outintbytesWritten);
- }
- publicpartialclassHMACSHA384{
- publicstaticbyte[]HashData(byte[]key,byte[]source);
- publicstaticbyte[]HashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source);
- publicstaticintHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination);
- publicstaticboolTryHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination,outintbytesWritten);
- }
- publicpartialclassHMACSHA512{
- publicstaticbyte[]HashData(byte[]key,byte[]source);
- publicstaticbyte[]HashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source);
- publicstaticintHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination);
- publicstaticboolTryHashData(ReadOnlySpan<byte>key,ReadOnlySpan<byte>source,Span<byte>destination,outintbytesWritten);
- }
- }
Sample Before
在之前的版本中想要实现计算 HMAC 算法会比较复杂,之前实现了一个 HashHelper 来封装了常用的 Hash 算法和 HMAC 算法,HashHelper 部分代码如下,完整代码可以从 Github 获取:https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Helpers/HashHelper.cs
- ///<summary>
- ///获取哈希之后的字符串
- ///</summary>
- ///<paramname="type">哈希类型</param>
- ///<paramname="source">源</param>
- ///<paramname="key">key</param>
- ///<paramname="isLower">是否是小写</param>
- ///<returns>哈希算法处理之后的字符串</returns>
- publicstaticstringGetHashedString(HashTypetype,byte[]source,byte[]?key,boolisLower=false)
- {
- Guard.NotNull(source,nameof(source));
- if(source.Length==0)
- {
- returnstring.Empty;
- }
- varhashedBytes=GetHashedBytes(type,source,key);
- varsbText=newStringBuilder();
- if(isLower)
- {
- foreach(varbinhashedBytes)
- {
- sbText.Append(b.ToString("x2"));
- }
- }
- else
- {
- foreach(varbinhashedBytes)
- {
- sbText.Append(b.ToString("X2"));
- }
- }
- returnsbText.ToString();
- }
- ///<summary>
- ///计算字符串Hash值
- ///</summary>
- ///<paramname="type">hash类型</param>
- ///<paramname="str">要hash的字符串</param>
- ///<returns>hash过的字节数组</returns>
- publicstaticbyte[]GetHashedBytes(HashTypetype,stringstr)=>GetHashedBytes(type,str,Encoding.UTF8);
- ///<summary>
- ///计算字符串Hash值
- ///</summary>
- ///<paramname="type">hash类型</param>
- ///<paramname="str">要hash的字符串</param>
- ///<paramname="encoding">编码类型</param>
- ///<returns>hash过的字节数组</returns>
- publicstaticbyte[]GetHashedBytes(HashTypetype,stringstr,Encodingencoding)
- {
- Guard.NotNull(str,nameof(str));
- if(str==string.Empty)
- {
- returnArray.Empty<byte>();
- }
- varbytes=encoding.GetBytes(str);
- returnGetHashedBytes(type,bytes);
- }
- ///<summary>
- ///获取Hash后的字节数组
- ///</summary>
- ///<paramname="type">哈希类型</param>
- ///<paramname="bytes">原字节数组</param>
- ///<returns></returns>
- publicstaticbyte[]GetHashedBytes(HashTypetype,byte[]bytes)=>GetHashedBytes(type,bytes,null);
- ///<summary>
- ///获取Hash后的字节数组
- ///</summary>
- ///<paramname="type">哈希类型</param>
- ///<paramname="key">key</param>
- ///<paramname="bytes">原字节数组</param>
- ///<returns></returns>
- publicstaticbyte[]GetHashedBytes(HashTypetype,byte[]bytes,byte[]?key)
- {
- Guard.NotNull(bytes,nameof(bytes));
- if(bytes.Length==0)
- {
- returnbytes;
- }
- HashAlgorithmalgorithm=null!;
- try
- {
- if(key==null)
- {
- algorithm=typeswitch
- {
- HashType.SHA1=>newSHA1Managed(),
- HashType.SHA256=>newSHA256Managed(),
- HashType.SHA384=>newSHA384Managed(),
- HashType.SHA512=>newSHA512Managed(),
- _=>MD5.Create()
- };
- }
- else
- {
- algorithm=typeswitch
- {
- HashType.SHA1=>newHMACSHA1(key),
- HashType.SHA256=>newHMACSHA256(key),
- HashType.SHA384=>newHMACSHA384(key),
- HashType.SHA512=>newHMACSHA512(key),
- _=>newHMACMD5(key)
- };
- }
- returnalgorithm.ComputeHash(bytes);
- }
- finally
- {
- algorithm.Dispose();
- }
- }
使用示例如下:
- 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 以后可以怎么简化呢,来看下面的示例:
- varbytes="test".GetBytes();
- varkeyBytes="test-key".GetBytes();
- //HMACMD5
- varhmd5V1=HMACMD5.HashData(keyBytes,bytes);
- varhmd5V2=HashHelper.GetHashedBytes(HashType.MD5,bytes,keyBytes);
- Console.WriteLine(hmd5V2.SequenceEqual(hmd5V1));
- //HMACSHA1
- varhsha1V1=HMACSHA1.HashData(keyBytes,bytes);
- varhsha1V2=HashHelper.GetHashedBytes(HashType.SHA1,bytes,keyBytes);
- Console.WriteLine(hsha1V2.SequenceEqual(hsha1V1));
- //HMACSHA256
- varhsha256V1=HMACSHA256.HashData(keyBytes,bytes);
- varhsha256V2=HashHelper.GetHashedBytes(HashType.SHA256,bytes,keyBytes);
- Console.WriteLine(hsha256V2.SequenceEqual(hsha256V1));
- //HMACSHA384
- varhsha384V1=HMACSHA384.HashData(keyBytes,bytes);
- varhsha384V2=HashHelper.GetHashedBytes(HashType.SHA384,bytes,keyBytes);
- Console.WriteLine(hsha384V2.SequenceEqual(hsha384V1));
- //HMACSHA512
- varhsha512V1=HMACSHA512.HashData(keyBytes,bytes);
- varhsha512V2=HashHelper.GetHashedBytes(HashType.SHA512,bytes,keyBytes);
- Console.WriteLine(hsha512V2.SequenceEqual(hsha512V1));
直接使用对应的 HMAC 哈希算法的 HashData 方法即可,传入对应的 key 和 原始内容就可以了,上面是和我们 HashHelper 封装的方法进行对比,看结果是否一致,都是一致的,输出结果如下:
More
对于普通的哈希算法,微软其实在 .NET 5 就已经支持了上面的用法,可以尝试一下下面的代码:
- varbytes="test".GetBytes();
- //MD5
- varmd5V1=MD5.HashData(bytes);
- varmd5V2=HashHelper.GetHashedBytes(HashType.MD5,bytes);
- Console.WriteLine(md5V2.SequenceEqual(md5V1));
- //SHA1
- varsha1V1=SHA1.HashData(bytes);
- varsha1V2=HashHelper.GetHashedBytes(HashType.SHA1,bytes);
- Console.WriteLine(sha1V2.SequenceEqual(sha1V1));
- //SHA256
- varsha256V1=SHA256.HashData(bytes);
- varsha256V2=HashHelper.GetHashedBytes(HashType.SHA256,bytes);
- Console.WriteLine(sha256V2.SequenceEqual(sha256V1));
- //SHA384
- varsha384V1=SHA384.HashData(bytes);
- varsha384V2=HashHelper.GetHashedBytes(HashType.SHA384,bytes);
- Console.WriteLine(sha384V2.SequenceEqual(sha384V1));
- //SHA512
- varsha512V1=SHA512.HashData(bytes);
- varsha512V2=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
原文链接:https://mp.weixin.qq.com/s/rbSGnq83mmUT5sV_vuyrcg