.NET 6 中哈希算法的简化用法

时间:2021-07-11 12:10:53

.NET 6 中哈希算法的简化用法

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

Sample Before

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

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

使用示例如下:

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

New API Sample

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

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

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

.NET 6 中哈希算法的简化用法

More

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

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