diff --git a/api_list.include.md b/api_list.include.md index b1131e31..a1d1ab73 100644 --- a/api_list.include.md +++ b/api_list.include.md @@ -1461,6 +1461,40 @@ * Note: When growing, new elements are default(T); the BCL exposes uninitialized data. +#### CryptographicOperations + + * `bool FixedTimeEquals(ReadOnlySpan, byte)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte)) + * Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + * `bool FixedTimeEquals(ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + * `byte[] HashData(HashAlgorithmName, byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte())) + * `int HashData(HashAlgorithmName, ReadOnlySpan, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte)))) + * `byte[] HashData(HashAlgorithmName, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte)))) + * `int HashData(HashAlgorithmName, Stream, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte)))) + * `byte[] HashData(HashAlgorithmName, Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream)) + * `ValueTask HashDataAsync(HashAlgorithmName, Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HashDataAsync(HashAlgorithmName, Stream, Memory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken)) + * `byte[] HmacData(HashAlgorithmName, byte[], byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte())) + * `byte[] HmacData(HashAlgorithmName, byte[], Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream)) + * `int HmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte)))) + * `byte[] HmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * `int HmacData(HashAlgorithmName, ReadOnlySpan, Stream, Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte)))) + * `byte[] HmacData(HashAlgorithmName, ReadOnlySpan, Stream)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream)) + * `ValueTask HmacDataAsync(HashAlgorithmName, byte[], Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HmacDataAsync(HashAlgorithmName, ReadOnlyMemory, Stream, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken)) + * `ValueTask HmacDataAsync(HashAlgorithmName, ReadOnlyMemory, Stream, Memory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken)) + * `bool TryHashData(HashAlgorithmName, ReadOnlySpan, Span, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0) + * `bool TryHmacData(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, Span, int)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0) + * `bool VerifyHmac(HashAlgorithmName, byte[], byte[], byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte())) + * `bool VerifyHmac(HashAlgorithmName, byte[], Stream, byte[])` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte())) + * `bool VerifyHmac(HashAlgorithmName, ReadOnlySpan, ReadOnlySpan, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte)))) + * `bool VerifyHmac(HashAlgorithmName, ReadOnlySpan, Stream, ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte)))) + * `ValueTask VerifyHmacAsync(HashAlgorithmName, byte[], Stream, byte[], CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken)) + * `ValueTask VerifyHmacAsync(HashAlgorithmName, ReadOnlyMemory, Stream, ReadOnlyMemory, CancellationToken)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken)) + * `void ZeroMemory(Span)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.zeromemory?view=net-11.0) + * Note: Best-effort zeroing; unlike the BCL intrinsic the JIT may elide the clear if the buffer is not observed afterwards. + + #### Utf16 * `int IndexOfInvalidSubsequence(ReadOnlySpan)` [reference](https://learn.microsoft.com/en-us/dotnet/api/system.text.unicode.utf16.indexofinvalidsubsequence?view=net-11.0) diff --git a/src/ApiBuilderTests/BuildApiTest.cs b/src/ApiBuilderTests/BuildApiTest.cs index cb3c6f5e..047b78a3 100644 --- a/src/ApiBuilderTests/BuildApiTest.cs +++ b/src/ApiBuilderTests/BuildApiTest.cs @@ -19,6 +19,7 @@ public Task RunWithRoslyn() WriteHelper("Lock", writer, ref count); WriteHelper(nameof(KeyValuePair), writer, ref count); WriteHelper("CollectionsMarshal", writer, ref count); + WriteHelper("CryptographicOperations", writer, ref count); WriteHelper("Utf16", writer, ref count); WriteType(nameof(TaskCompletionSource), writer, ref count); WriteType(nameof(UnreachableException), writer, ref count); diff --git a/src/Consume/Consume.cs b/src/Consume/Consume.cs index 74daab23..443df782 100644 --- a/src/Consume/Consume.cs +++ b/src/Consume/Consume.cs @@ -416,6 +416,53 @@ void MD5Usage() MD5.HashDataAsync(source: null!, destination: memory); MD5.HashDataAsync(source: null!, destination: memory, cancellationToken: CancellationToken.None); #endif +#endif + } + + void CryptographicOperationsUsage() + { + var name = HashAlgorithmName.SHA256; + + CryptographicOperations.HashData(name, (byte[]) null!); + CryptographicOperations.HashData(name, (Stream) null!); + CryptographicOperations.HmacData(name, (byte[]) null!, (byte[]) null!); + CryptographicOperations.HmacData(name, (byte[]) null!, (Stream) null!); + CryptographicOperations.VerifyHmac(name, (byte[]) null!, (byte[]) null!, (byte[]) null!); + CryptographicOperations.VerifyHmac(name, (byte[]) null!, (Stream) null!, (byte[]) null!); +#if FeatureValueTask + CryptographicOperations.HashDataAsync(name, (Stream) null!, CancellationToken.None); + CryptographicOperations.HmacDataAsync(name, (byte[]) null!, (Stream) null!, CancellationToken.None); + CryptographicOperations.VerifyHmacAsync(name, (byte[]) null!, (Stream) null!, (byte[]) null!, CancellationToken.None); +#endif +#if FeatureMemory + Span span = default; + ReadOnlySpan readOnlySpan = default; + Memory memory = default; + ReadOnlyMemory readOnlyMemory = default; + + CryptographicOperations.FixedTimeEquals(readOnlySpan, readOnlySpan); + CryptographicOperations.FixedTimeEquals(readOnlySpan, (byte) 0); + CryptographicOperations.ZeroMemory(span); + CryptographicOperations.HashData(name, readOnlySpan); + CryptographicOperations.HashData(name, readOnlySpan, span); + CryptographicOperations.HashData(name, (Stream) null!, span); + CryptographicOperations.TryHashData(name, readOnlySpan, span, out _); + CryptographicOperations.HmacData(name, readOnlySpan, readOnlySpan); + CryptographicOperations.HmacData(name, readOnlySpan, readOnlySpan, span); + CryptographicOperations.HmacData(name, readOnlySpan, (Stream) null!); + CryptographicOperations.HmacData(name, readOnlySpan, (Stream) null!, span); + CryptographicOperations.TryHmacData(name, readOnlySpan, readOnlySpan, span, out _); + CryptographicOperations.VerifyHmac(name, readOnlySpan, readOnlySpan, readOnlySpan); + CryptographicOperations.VerifyHmac(name, readOnlySpan, (Stream) null!, readOnlySpan); +#if FeatureValueTask + CryptographicOperations.HashDataAsync(name, (Stream) null!, memory); + CryptographicOperations.HashDataAsync(name, (Stream) null!, memory, CancellationToken.None); + CryptographicOperations.HmacDataAsync(name, readOnlyMemory, (Stream) null!); + CryptographicOperations.HmacDataAsync(name, readOnlyMemory, (Stream) null!, CancellationToken.None); + CryptographicOperations.HmacDataAsync(name, readOnlyMemory, (Stream) null!, memory, CancellationToken.None); + CryptographicOperations.VerifyHmacAsync(name, readOnlyMemory, (Stream) null!, readOnlyMemory); + CryptographicOperations.VerifyHmacAsync(name, readOnlyMemory, (Stream) null!, readOnlyMemory, CancellationToken.None); +#endif #endif } diff --git a/src/Polyfill/CryptographicOperations.cs b/src/Polyfill/CryptographicOperations.cs new file mode 100644 index 00000000..5f5694c0 --- /dev/null +++ b/src/Polyfill/CryptographicOperations.cs @@ -0,0 +1,531 @@ +#pragma warning disable + +// CryptographicOperations was introduced in netcoreapp2.1/netstandard2.1. +// For targets where the type is entirely absent it is recreated here. +// For targets where the type exists but is missing members (members were added +// in net8/net9/net10) the missing members are added via extension in +// CryptographicOperationsExtensions.cs. +#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1_OR_GREATER + +namespace System.Security.Cryptography; + +// ReSharper disable RedundantUsingDirective +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +// ReSharper restore RedundantUsingDirective +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +//Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations?view=net-11.0 +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte()) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + +#if FeatureValueTask + + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } + +#endif + +#if FeatureMemory + + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + //Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + + return accum == 0; + } + + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte) + //Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + + return accum == 0; + } + + /// + /// Fills the provided buffer with zeros. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.zeromemory?view=net-11.0 + //Note: Best-effort zeroing; unlike the BCL intrinsic the JIT may elide the clear if the buffer is not observed afterwards. + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + hash.CopyTo(destination); + return hash.Length; + } + + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0 + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + mac.CopyTo(destination); + return mac.Length; + } + + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0 + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } + +#if FeatureValueTask + + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + +#endif +#endif + + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + + return accum == 0; + } +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] +#endif diff --git a/src/Polyfill/CryptographicOperationsExtensions.cs b/src/Polyfill/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..8d074045 --- /dev/null +++ b/src/Polyfill/CryptographicOperationsExtensions.cs @@ -0,0 +1,461 @@ +#pragma warning disable + +// Fills in members missing from the BCL CryptographicOperations on targets where +// the type exists (netcoreapp2.1/netstandard2.1 onwards) but predates the member: +// HashData family added in net8, HmacData family in net9, VerifyHmac family and +// FixedTimeEquals(ReadOnlySpan, byte) in net10. +// For targets where the type is entirely absent the full surface is recreated in +// CryptographicOperations.cs instead. Span/ValueTask/ReadOnlyMemory are intrinsic +// to every target in this window, so no Feature* gates are needed here. +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + +namespace Polyfills; + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +static partial class Polyfill +{ + extension(CryptographicOperations) + { +#if !NET9_0_OR_GREATER + + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-byte()) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the hash of data using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream) + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdata(system-security-cryptography-hashalgorithmname-system-io-stream-system-span((system-byte))) + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + hash.CopyTo(destination); + return hash.Length; + } + + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhashdata?view=net-11.0 + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hashdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hashdataasync(system-security-cryptography-hashalgorithmname-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + +#endif + +#if !NET9_0_OR_GREATER + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + return bytesWritten; + } + + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream) + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdata?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdata(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-span((system-byte))) + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + + mac.CopyTo(destination); + return mac.Length; + } + + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.tryhmacdata?view=net-11.0 + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.hmacdataasync?view=net-11.0#system-security-cryptography-cryptographicoperations-hmacdataasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-memory((system-byte))-system-threading-cancellationtoken) + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + +#endif + +#if !NET11_0_OR_GREATER + + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals?view=net-11.0#system-security-cryptography-cryptographicoperations-fixedtimeequals(system-readonlyspan((system-byte))-system-byte) + //Note: Best-effort constant-time comparison; a managed loop cannot offer the same guarantees as the BCL intrinsic. + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + + return accum == 0; + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-byte()-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmac?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmac(system-security-cryptography-hashalgorithmname-system-readonlyspan((system-byte))-system-io-stream-system-readonlyspan((system-byte))) + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-byte()-system-io-stream-system-byte()-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + //Link: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.verifyhmacasync?view=net-11.0#system-security-cryptography-cryptographicoperations-verifyhmacasync(system-security-cryptography-hashalgorithmname-system-readonlymemory((system-byte))-system-io-stream-system-readonlymemory((system-byte))-system-threading-cancellationtoken) + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + +#endif + } + +#if !NET9_0_OR_GREATER + + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + +#endif + +#if !NET11_0_OR_GREATER + + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + +#endif +} + +#endif diff --git a/src/Split/net10.0/CryptographicOperationsExtensions.cs b/src/Split/net10.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..83fc0dc3 --- /dev/null +++ b/src/Split/net10.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,131 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net10.0/TypeForwardeds.cs b/src/Split/net10.0/TypeForwardeds.cs index b95ad748..8cfaee14 100644 --- a/src/Split/net10.0/TypeForwardeds.cs +++ b/src/Split/net10.0/TypeForwardeds.cs @@ -9,6 +9,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] diff --git a/src/Split/net11.0/TypeForwardeds.cs b/src/Split/net11.0/TypeForwardeds.cs index 22e9145a..ac920b51 100644 --- a/src/Split/net11.0/TypeForwardeds.cs +++ b/src/Split/net11.0/TypeForwardeds.cs @@ -9,6 +9,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] diff --git a/src/Split/net461/CryptographicOperations.cs b/src/Split/net461/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net461/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net462/CryptographicOperations.cs b/src/Split/net462/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net462/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net47/CryptographicOperations.cs b/src/Split/net47/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net47/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net471/CryptographicOperations.cs b/src/Split/net471/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net471/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net472/CryptographicOperations.cs b/src/Split/net472/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net472/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net48/CryptographicOperations.cs b/src/Split/net48/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net48/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net481/CryptographicOperations.cs b/src/Split/net481/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/net481/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/net5.0/CryptographicOperationsExtensions.cs b/src/Split/net5.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net5.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net5.0/TypeForwardeds.cs b/src/Split/net5.0/TypeForwardeds.cs index d28d5c38..d5dc2b38 100644 --- a/src/Split/net5.0/TypeForwardeds.cs +++ b/src/Split/net5.0/TypeForwardeds.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/net6.0/CryptographicOperationsExtensions.cs b/src/Split/net6.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net6.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net6.0/TypeForwardeds.cs b/src/Split/net6.0/TypeForwardeds.cs index 00408b26..f04df8cf 100644 --- a/src/Split/net6.0/TypeForwardeds.cs +++ b/src/Split/net6.0/TypeForwardeds.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] diff --git a/src/Split/net7.0/CryptographicOperationsExtensions.cs b/src/Split/net7.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net7.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net7.0/TypeForwardeds.cs b/src/Split/net7.0/TypeForwardeds.cs index acbb458e..780f168c 100644 --- a/src/Split/net7.0/TypeForwardeds.cs +++ b/src/Split/net7.0/TypeForwardeds.cs @@ -5,6 +5,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.InteropServices.CollectionsMarshal))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] diff --git a/src/Split/net8.0/CryptographicOperationsExtensions.cs b/src/Split/net8.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/net8.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net8.0/TypeForwardeds.cs b/src/Split/net8.0/TypeForwardeds.cs index d3b8dcb1..b511da9e 100644 --- a/src/Split/net8.0/TypeForwardeds.cs +++ b/src/Split/net8.0/TypeForwardeds.cs @@ -7,6 +7,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ExperimentalAttribute))] diff --git a/src/Split/net9.0/CryptographicOperationsExtensions.cs b/src/Split/net9.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..83fc0dc3 --- /dev/null +++ b/src/Split/net9.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,131 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/net9.0/TypeForwardeds.cs b/src/Split/net9.0/TypeForwardeds.cs index dc347bc9..db89f0fa 100644 --- a/src/Split/net9.0/TypeForwardeds.cs +++ b/src/Split/net9.0/TypeForwardeds.cs @@ -8,6 +8,7 @@ [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] [assembly: TypeForwardedTo(typeof(System.Threading.Tasks.ConfigureAwaitOptions))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ConstantExpectedAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.Diagnostics.DebuggerDisableUserUnhandledExceptionsAttribute))] [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] diff --git a/src/Split/netcoreapp2.0/CryptographicOperations.cs b/src/Split/netcoreapp2.0/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/netcoreapp2.0/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp2.1/TypeForwardeds.cs b/src/Split/netcoreapp2.1/TypeForwardeds.cs index 14b514da..e66754ff 100644 --- a/src/Split/netcoreapp2.1/TypeForwardeds.cs +++ b/src/Split/netcoreapp2.1/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp2.2/TypeForwardeds.cs b/src/Split/netcoreapp2.2/TypeForwardeds.cs index 14b514da..e66754ff 100644 --- a/src/Split/netcoreapp2.2/TypeForwardeds.cs +++ b/src/Split/netcoreapp2.2/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp3.0/TypeForwardeds.cs b/src/Split/netcoreapp3.0/TypeForwardeds.cs index d2422ae3..98847ca2 100644 --- a/src/Split/netcoreapp3.0/TypeForwardeds.cs +++ b/src/Split/netcoreapp3.0/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netcoreapp3.1/TypeForwardeds.cs b/src/Split/netcoreapp3.1/TypeForwardeds.cs index d2422ae3..98847ca2 100644 --- a/src/Split/netcoreapp3.1/TypeForwardeds.cs +++ b/src/Split/netcoreapp3.1/TypeForwardeds.cs @@ -2,6 +2,7 @@ #pragma warning disable using System.Runtime.CompilerServices; [assembly: TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/netstandard2.0/CryptographicOperations.cs b/src/Split/netstandard2.0/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/netstandard2.0/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs b/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs new file mode 100644 index 00000000..c1ff795c --- /dev/null +++ b/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs @@ -0,0 +1,352 @@ +// +#pragma warning disable +#pragma warning disable +namespace Polyfills; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; +static partial class Polyfill +{ + extension(CryptographicOperations) + { + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = CryptographicOperations.HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!CryptographicOperations.TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = CryptographicOperations.HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), new ReadOnlySpan(source), new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + return CryptographicOperations.VerifyHmac(hashAlgorithm, new ReadOnlySpan(key), source, new ReadOnlySpan(hash)); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return CryptographicOperations.FixedTimeEquals(mac, hash); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key, source, hash)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(CryptographicOperations.VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } + } + static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } +} diff --git a/src/Split/netstandard2.1/TypeForwardeds.cs b/src/Split/netstandard2.1/TypeForwardeds.cs index f65793fe..2c58215a 100644 --- a/src/Split/netstandard2.1/TypeForwardeds.cs +++ b/src/Split/netstandard2.1/TypeForwardeds.cs @@ -1,6 +1,7 @@ // #pragma warning disable using System.Runtime.CompilerServices; +[assembly: TypeForwardedTo(typeof(System.Security.Cryptography.CryptographicOperations))] [assembly: TypeForwardedTo(typeof(System.IO.EnumerationOptions))] [assembly: TypeForwardedTo(typeof(System.Collections.Generic.KeyValuePair))] [assembly: TypeForwardedTo(typeof(System.IO.MatchCasing))] diff --git a/src/Split/uap10.0/CryptographicOperations.cs b/src/Split/uap10.0/CryptographicOperations.cs new file mode 100644 index 00000000..461c3eee --- /dev/null +++ b/src/Split/uap10.0/CryptographicOperations.cs @@ -0,0 +1,420 @@ +// +#pragma warning disable +#pragma warning disable +namespace System.Security.Cryptography; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +/// +/// Provides methods for common cryptographic operations and reducing side-channel information leakage. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +#if PolyUseEmbeddedAttribute +[global::Microsoft.CodeAnalysis.EmbeddedAttribute] +#endif +#if PolyPublic +public +#endif +static class CryptographicOperations +{ + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the hash of a stream using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source); + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + return hmac.ComputeHash(source); + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + using var hmac = CreateHmac(hashAlgorithm, key); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEqualsCore(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key, source)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, byte[] hash, CancellationToken cancellationToken = default) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (hash == null) + { + throw new ArgumentNullException(nameof(hash)); + } + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key, source, hash)); + } +#endif +#if FeatureMemory + /// + /// Determines the equality of two byte sequences in an amount of time that depends on the length of the sequences, but not their values. + /// + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + { + return false; + } + var length = left.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } + /// + /// Determines whether every byte in is equal to , in an amount of time that depends on the length of , but not its values. + /// + public static bool FixedTimeEquals(ReadOnlySpan source, byte value) + { + var length = source.Length; + var accum = 0; + for (var i = 0; i < length; i++) + { + accum |= source[i] - value; + } + return accum == 0; + } + /// + /// Fills the provided buffer with zeros. + /// + public static void ZeroMemory(Span buffer) => + buffer.Clear(); + /// + /// Computes the hash of data using the specified algorithm. + /// + public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + return hasher.ComputeHash(source.ToArray()); + } + /// + /// Computes the hash of data into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination) + { + if (!TryHashData(hashAlgorithm, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span destination) + { + var hash = HashData(hashAlgorithm, source); + if (destination.Length < hash.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + hash.CopyTo(destination); + return hash.Length; + } + /// + /// Attempts to compute the hash of data into the provided destination using the specified algorithm. + /// + public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hasher = CreateHashAlgorithm(hashAlgorithm); + var hash = hasher.ComputeHash(source.ToArray()); + if (destination.Length < hash.Length) + { + bytesWritten = 0; + return false; + } + hash.CopyTo(destination); + bytesWritten = hash.Length; + return true; + } + /// + /// Computes the HMAC of data using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source.ToArray()); + } + /// + /// Computes the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination) + { + if (!TryHmacData(hashAlgorithm, key, source, destination, out var bytesWritten)) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + return bytesWritten; + } + /// + /// Computes the HMAC of a stream using the specified key and algorithm. + /// + public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + return hmac.ComputeHash(source); + } + /// + /// Computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, Span destination) + { + var mac = HmacData(hashAlgorithm, key, source); + if (destination.Length < mac.Length) + { + throw new ArgumentException("Destination is too short.", nameof(destination)); + } + mac.CopyTo(destination); + return mac.Length; + } + /// + /// Attempts to compute the HMAC of data into the provided destination using the specified key and algorithm. + /// + public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var mac = hmac.ComputeHash(source.ToArray()); + if (destination.Length < mac.Length) + { + bytesWritten = 0; + return false; + } + mac.CopyTo(destination); + bytesWritten = mac.Length; + return true; + } + /// + /// Determines whether the HMAC of matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) + { + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source.ToArray()); + return FixedTimeEquals(mac, hash); + } + /// + /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. + /// + public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, Stream source, ReadOnlySpan hash) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + using var hmac = CreateHmac(hashAlgorithm, key.ToArray()); + var hashSizeInBytes = hmac.HashSize / 8; + if (hash.Length != hashSizeInBytes) + { + throw new ArgumentException($"The hash must be {hashSizeInBytes} bytes in length.", nameof(hash)); + } + var mac = hmac.ComputeHash(source); + return FixedTimeEquals(mac, hash); + } +#if FeatureValueTask + /// + /// Asynchronously computes the hash of a stream into the provided destination using the specified algorithm. + /// + public static ValueTask HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HashData(hashAlgorithm, source, destination.Span)); + } + /// + /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source)); + } + /// + /// Asynchronously computes the HMAC of a stream into the provided destination using the specified key and algorithm. + /// + public static ValueTask HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, Memory destination, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(HmacData(hashAlgorithm, key.Span, source, destination.Span)); + } + /// + /// Asynchronously determines whether the HMAC of the stream matches . + /// + public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(VerifyHmac(hashAlgorithm, key.Span, source, hash.Span)); + } +#endif +#endif + static HashAlgorithm CreateHashAlgorithm(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.Create(), + "SHA1" => SHA1.Create(), + "SHA384" => SHA384.Create(), + "SHA512" => SHA512.Create(), + "MD5" => MD5.Create(), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => new HMACSHA256(key), + "SHA1" => new HMACSHA1(key), + "SHA384" => new HMACSHA384(key), + "SHA512" => new HMACSHA512(key), + "MD5" => new HMACMD5(key), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool FixedTimeEqualsCore(byte[] left, byte[] right) + { + if (left.Length != right.Length) + { + return false; + } + var accum = 0; + for (var i = 0; i < left.Length; i++) + { + accum |= left[i] - right[i]; + } + return accum == 0; + } +} diff --git a/src/Tests/CryptographicOperationsPolyfillTests.cs b/src/Tests/CryptographicOperationsPolyfillTests.cs new file mode 100644 index 00000000..1147cae9 --- /dev/null +++ b/src/Tests/CryptographicOperationsPolyfillTests.cs @@ -0,0 +1,255 @@ +using System.Security.Cryptography; + +public class CryptographicOperationsPolyfillTests +{ + static byte[] data = [1, 2, 3, 4, 5]; + static byte[] key = [10, 20, 30, 40, 50, 60]; + + static byte[] ExpectedSha256() + { + using var sha = SHA256.Create(); + return sha.ComputeHash(data); + } + + static byte[] ExpectedHmacSha256() + { + using var hmac = new HMACSHA256(key); + return hmac.ComputeHash(data); + } + + [Test] + public async Task HashData_ByteArray() + { + var actual = CryptographicOperations.HashData(HashAlgorithmName.SHA256, data); + await Assert.That(actual.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HashData_ByteArray_Null_Throws() => + await Assert.That(() => CryptographicOperations.HashData(HashAlgorithmName.SHA256, (byte[]) null!)) + .Throws(); + + [Test] + public async Task HashData_Stream() + { + using var stream = new MemoryStream(data); + var actual = CryptographicOperations.HashData(HashAlgorithmName.SHA256, stream); + await Assert.That(actual.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HmacData_ByteArray() + { + var actual = CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key, data); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task HmacData_Stream() + { + using var stream = new MemoryStream(data); + var actual = CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key, stream); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmac_ByteArray_Valid() + { + var mac = ExpectedHmacSha256(); + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, data, mac)).IsTrue(); + } + + [Test] + public async Task VerifyHmac_ByteArray_Tampered() + { + var mac = ExpectedHmacSha256(); + mac[0] ^= 0xFF; + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, data, mac)).IsFalse(); + } + + [Test] + public async Task VerifyHmac_Stream_Valid() + { + using var stream = new MemoryStream(data); + var mac = ExpectedHmacSha256(); + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, stream, mac)).IsTrue(); + } + +#if FeatureValueTask + [Test] + public async Task HashDataAsync_Stream() + { + using var stream = new MemoryStream(data); + var actual = await CryptographicOperations.HashDataAsync(HashAlgorithmName.SHA256, stream); + await Assert.That(actual.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HmacDataAsync_ByteArray_Stream() + { + using var stream = new MemoryStream(data); + var actual = await CryptographicOperations.HmacDataAsync(HashAlgorithmName.SHA256, key, stream); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmacAsync_ByteArray_Stream() + { + using var stream = new MemoryStream(data); + var mac = ExpectedHmacSha256(); + var result = await CryptographicOperations.VerifyHmacAsync(HashAlgorithmName.SHA256, key, stream, mac); + await Assert.That(result).IsTrue(); + } +#endif + +#if FeatureMemory + [Test] + public async Task FixedTimeEquals_Equal() + { + var left = new byte[] {1, 2, 3, 4}; + var right = new byte[] {1, 2, 3, 4}; + await Assert.That(CryptographicOperations.FixedTimeEquals(left, right)).IsTrue(); + } + + [Test] + public async Task FixedTimeEquals_NotEqual() + { + var left = new byte[] {1, 2, 3, 4}; + var right = new byte[] {1, 2, 3, 5}; + await Assert.That(CryptographicOperations.FixedTimeEquals(left, right)).IsFalse(); + } + + [Test] + public async Task FixedTimeEquals_DifferentLength() + { + var left = new byte[] {1, 2, 3, 4}; + var right = new byte[] {1, 2, 3}; + await Assert.That(CryptographicOperations.FixedTimeEquals(left, right)).IsFalse(); + } + + [Test] + public async Task FixedTimeEquals_SingleByte_AllMatch() + { + var source = new byte[] {7, 7, 7, 7}; + await Assert.That(CryptographicOperations.FixedTimeEquals(source, (byte) 7)).IsTrue(); + } + + [Test] + public async Task FixedTimeEquals_SingleByte_Mismatch() + { + var source = new byte[] {7, 7, 8, 7}; + await Assert.That(CryptographicOperations.FixedTimeEquals(source, (byte) 7)).IsFalse(); + } + + [Test] + public async Task ZeroMemory_Clears() + { + var buffer = new byte[] {1, 2, 3, 4, 5}; + CryptographicOperations.ZeroMemory(buffer); + await Assert.That(buffer.All(_ => _ == 0)).IsTrue(); + } + + [Test] + public async Task HashData_Span() + { + var destination = new byte[32]; + var written = CryptographicOperations.HashData(HashAlgorithmName.SHA256, data.AsSpan(), destination); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HashData_Span_DestinationTooShort() + { + var exception = await Assert.That(() => + { + var destination = new byte[8]; + CryptographicOperations.HashData(HashAlgorithmName.SHA256, data.AsSpan(), destination); + }) + .Throws(); + await Assert.That(exception!.Message.StartsWith("Destination is too short.")).IsTrue(); + } + + [Test] + public async Task TryHashData_Valid() + { + var destination = new byte[32]; + var result = CryptographicOperations.TryHashData(HashAlgorithmName.SHA256, data.AsSpan(), destination, out var written); + await Assert.That(result).IsTrue(); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task TryHashData_TooShort() + { + var destination = new byte[8]; + var result = CryptographicOperations.TryHashData(HashAlgorithmName.SHA256, data.AsSpan(), destination, out var written); + await Assert.That(result).IsFalse(); + await Assert.That(written).IsEqualTo(0); + } + + [Test] + public async Task HmacData_Span() + { + var actual = CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan()); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task TryHmacData_Valid() + { + var destination = new byte[32]; + var result = CryptographicOperations.TryHmacData(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), destination, out var written); + await Assert.That(result).IsTrue(); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmac_Span_Valid() + { + var mac = ExpectedHmacSha256(); + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), mac.AsSpan())).IsTrue(); + } + + [Test] + public async Task VerifyHmac_Span_Tampered() + { + var mac = ExpectedHmacSha256(); + mac[0] ^= 0xFF; + await Assert.That(CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), mac.AsSpan())).IsFalse(); + } + +#if FeatureValueTask + [Test] + public async Task HashDataAsync_Memory() + { + using var stream = new MemoryStream(data); + Memory destination = new byte[32]; + var written = await CryptographicOperations.HashDataAsync(HashAlgorithmName.SHA256, stream, destination); + await Assert.That(written).IsEqualTo(32); + await Assert.That(destination.ToArray().SequenceEqual(ExpectedSha256())).IsTrue(); + } + + [Test] + public async Task HmacDataAsync_Memory() + { + using var stream = new MemoryStream(data); + ReadOnlyMemory keyMemory = key; + var actual = await CryptographicOperations.HmacDataAsync(HashAlgorithmName.SHA256, keyMemory, stream); + await Assert.That(actual.SequenceEqual(ExpectedHmacSha256())).IsTrue(); + } + + [Test] + public async Task VerifyHmacAsync_Memory() + { + using var stream = new MemoryStream(data); + ReadOnlyMemory keyMemory = key; + ReadOnlyMemory macMemory = ExpectedHmacSha256(); + var result = await CryptographicOperations.VerifyHmacAsync(HashAlgorithmName.SHA256, keyMemory, stream, macMemory); + await Assert.That(result).IsTrue(); + } +#endif +#endif +}