当前位置 博文首页 > 无欲则刚的博客:.NET 6 中哈希算法的简化用法

    无欲则刚的博客:.NET 6 中哈希算法的简化用法

    作者:[db:作者] 时间:2021-09-10 18:59

    介绍

    微软在 .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 定义如下:

    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

    ///?<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();
    ????}
    }
    

    使用示例如下:

    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 以后可以怎么简化呢,来看下面的示例:

    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?封装的方法进行对比,看结果是否一致,都是一致的,输出结果如下:

    ?

    More

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

    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?使用起来更舒服一些,哈哈,这样的静态方法不够抽象如果要动态替换哈希算法代码可能就有点...

    cs