當前位置: 妍妍網 > 碼農

.NET 9 Preview 1 支持 HashAlgorithm 的 one-shot Hash

2024-02-25碼農

.NET 9 Preview 1 支持 演算法 的 one-shot Hash

Intro

在之前的 .NET 版本裏已經引入了一些 One-shot hash 的計算方式,如 SHA1.HashData / HMACSHA1.HashData 等方法

.NET 9 Preview 1 中引入了可以指定哈希演算法的快速哈希方法,我們希望根據配置使用不同的哈希型別來獲取哈希時可以很好的簡化我們的程式碼

New API

新的 API 如下:

namespaceSystem.Security.Cryptography;
// Existing class
publicstaticpartial classCryptographicOperations {
publicstaticbyte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source);
publicstaticbyte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
publicstaticintHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
publicstaticboolTryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, outint bytesWritten);
publicstaticbyte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source);
publicstaticbyte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source);
publicstaticintHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source, Span<byte> destination);
publicstatic ValueTask<byte[]> HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default);
publicstatic ValueTask<intHmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, CancellationToken cancellationToken = default);
publicstatic ValueTask<byte[]> HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, CancellationToken cancellationToken = default);
publicstaticbyte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source);
publicstaticbyte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source);
publicstaticintHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination);
publicstaticboolTryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination, outint bytesWritten);
publicstaticbyte[] HashData(HashAlgorithmName hashAlgorithm, Stream source);
publicstaticintHashData(HashAlgorithmName hashAlgorithm, Stream source, Span<byte> destination);
publicstatic ValueTask<intHashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory<byte> destination, CancellationToken cancellationToken = default);
publicstatic ValueTask<byte[]> HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default);
}

主要分成了兩類,一類是 HashData ,直接計算哈希,一類是 HmacData Hmac 和直接的 Hash 相比,多了一個 key,更加具有抗碰撞性,TOTP 演算法就是基於 HMAC 哈希演算法來實作的

常用的 MD5/SHA1/SHA256 都是支持的,除此之外,還支持更安全的 SHA3 系列的 Hash 演算法

Sample

可以對比一下新的哈希演算法的 hash 方法和之前的 one-shot hash 的結果是否一致:

var helloBytes = "Hello World"u8;
var keyBytes = "keys"u8;
var sha1Bytes = SHA1.HashData(helloBytes);
var sha1HashedBytes = CryptographicOperations.HashData(HashAlgorithmName.SHA1, helloBytes);
Console.WriteLine(sha1HashedBytes.SequenceEqual(sha1Bytes));
var sha1MacBytes = HMACSHA1.HashData(keyBytes, helloBytes);
var sha1HmacBytes = CryptographicOperations.HmacData(HashAlgorithmName.SHA1, keyBytes, helloBytes);
Console.WriteLine(sha1HmacBytes.SequenceEqual(sha1MacBytes));

這裏使用了 SHA1 / HMACSHA1 做了一個對比,比較兩種方式的輸出結果是否一致,輸出結果均為 True

再來試一下其他的演算法,使用範例如下:

var helloBytes = "Hello World"u8;
var keyBytes = "keys"u8;
PrintHashString(HashAlgorithmName.MD5, helloBytes);
PrintHashString(HashAlgorithmName.SHA1, helloBytes);
PrintHashString(HashAlgorithmName.SHA256, helloBytes);
PrintHashString(HashAlgorithmName.SHA384, helloBytes);
PrintHashString(HashAlgorithmName.SHA512, helloBytes);
// sha3
PrintHashString(HashAlgorithmName.SHA3_256, helloBytes);
PrintHashString(HashAlgorithmName.SHA3_384, helloBytes);
PrintHashString(HashAlgorithmName.SHA3_512, helloBytes);
PrintHmacString(HashAlgorithmName.MD5, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA1, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA256, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA384, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA512, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA3_256, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA3_384, keyBytes, helloBytes);
PrintHmacString(HashAlgorithmName.SHA3_512, keyBytes, helloBytes);
Console.WriteLine("Completed");

privatestaticvoidPrintHashString(HashAlgorithmName hashAlgorithmName, ReadOnlySpan<byte> bytes)
{
var hashedBytes = CryptographicOperations.HashData(hashAlgorithmName, bytes);
var hexString = Convert.ToHexString(hashedBytes);
Console.WriteLine($"{hashAlgorithmName}{hexString}");
}
privatestaticvoidPrintHmacString(HashAlgorithmName hashAlgorithmName, ReadOnlySpan<byte> key, ReadOnlySpan<byte> bytes)
{
var hashedBytes = CryptographicOperations.HmacData(hashAlgorithmName, key, bytes);
var hexString = Convert.ToHexString(hashedBytes);
Console.WriteLine($"{hashAlgorithmName}{hexString}");
}



輸出結果如下:

output

More

前面的範例只演示了同步方法中 ReadOnlySpan 參數返回字節陣列方法的使用,還支持異步方法以及從一個字節陣列或者 Stream ,以及輸出到一個 Span 中,並且支持異步方法,大家可以根據需要自己嘗試一下,並根據進行選擇使用

References

  • https://github.com/dotnet/runtime/issues/91407

  • https://github.com/dotnet/runtime/issues/62489

  • https://github.com/dotnet/runtime/issues/17590

  • https://github.com/dotnet/runtime/issues/40012

  • https://github.com/dotnet/runtime/pull/92430

  • https://cryptobook.nakov.com/cryptographic-hash-functions/secure-hash-algorithms

  • https://github.com/WeihanLi/SamplesInPractice/blob/main/net9sample/Net9Samples/HashSample.cs