diff --git a/src/Polyfill/CryptographicOperationsExtensions.cs b/src/Polyfill/CryptographicOperationsExtensions.cs index fcec6565..bd5c6301 100644 --- a/src/Polyfill/CryptographicOperationsExtensions.cs +++ b/src/Polyfill/CryptographicOperationsExtensions.cs @@ -34,8 +34,12 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) throw new ArgumentNullException(nameof(source)); } +#if NET5_0_OR_GREATER + return CryptographicOperationsHashData(hashAlgorithm, source); +#else using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); return hasher.ComputeHash(source); +#endif } /// @@ -44,8 +48,14 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) //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) { +#if NET5_0_OR_GREATER + return CryptographicOperationsHashData(hashAlgorithm, source); +#else using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source.ToArray()); + var hash = new byte[hasher.HashSize / 8]; + hasher.TryComputeHash(source, hash, out _); + return hash; +#endif } /// @@ -99,17 +109,12 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< //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) { +#if NET5_0_OR_GREATER + return CryptographicOperationsTryHashData(hashAlgorithm, source, destination, out bytesWritten); +#else 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; + return hasher.TryComputeHash(source, destination, out bytesWritten); +#endif } /// @@ -146,8 +151,12 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[ throw new ArgumentNullException(nameof(source)); } +#if NET6_0_OR_GREATER + return CryptographicOperationsHmacData(hashAlgorithm, key, source); +#else using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); return hmac.ComputeHash(source); +#endif } /// @@ -176,8 +185,14 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea //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) { +#if NET6_0_OR_GREATER + return CryptographicOperationsHmacData(hashAlgorithm, key, source); +#else using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; +#endif } /// @@ -231,17 +246,12 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k //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) { +#if NET6_0_OR_GREATER + return CryptographicOperationsTryHmacData(hashAlgorithm, key, source, destination, out bytesWritten); +#else 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); +#endif } /// @@ -342,15 +352,15 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea //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; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// @@ -422,8 +432,99 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam #endif +#if NET5_0_OR_GREATER && !NET9_0_OR_GREATER + + // The static one-shots hash without creating an algorithm instance or copying the input + static byte[] CryptographicOperationsHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.HashData(source), + "SHA1" => SHA1.HashData(source), + "SHA384" => SHA384.HashData(source), + "SHA512" => SHA512.HashData(source), + "MD5" => MD5.HashData(source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + + static bool CryptographicOperationsTryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return SHA256.TryHashData(source, destination, out bytesWritten); + case "SHA1": + return SHA1.TryHashData(source, destination, out bytesWritten); + case "SHA384": + return SHA384.TryHashData(source, destination, out bytesWritten); + case "SHA512": + return SHA512.TryHashData(source, destination, out bytesWritten); + case "MD5": + return MD5.TryHashData(source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + +#endif + +#if NET6_0_OR_GREATER && !NET9_0_OR_GREATER + + static byte[] CryptographicOperationsHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => HMACSHA256.HashData(key, source), + "SHA1" => HMACSHA1.HashData(key, source), + "SHA384" => HMACSHA384.HashData(key, source), + "SHA512" => HMACSHA512.HashData(key, source), + "MD5" => HMACMD5.HashData(key, source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + + static bool CryptographicOperationsTryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return HMACSHA256.TryHashData(key, source, destination, out bytesWritten); + case "SHA1": + return HMACSHA1.TryHashData(key, source, destination, out bytesWritten); + case "SHA384": + return HMACSHA384.TryHashData(key, source, destination, out bytesWritten); + case "SHA512": + return HMACSHA512.TryHashData(key, source, destination, out bytesWritten); + case "MD5": + return HMACMD5.TryHashData(key, source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + +#endif + #if !NET11_0_OR_GREATER + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/net10.0/CryptographicOperationsExtensions.cs b/src/Split/net10.0/CryptographicOperationsExtensions.cs index 1d93d09b..52295551 100644 --- a/src/Split/net10.0/CryptographicOperationsExtensions.cs +++ b/src/Split/net10.0/CryptographicOperationsExtensions.cs @@ -63,14 +63,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -111,6 +111,19 @@ public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, b public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) => new(VerifyHashStreamAsync(CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()), source, hash.ToArray(), cancellationToken)); } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/net5.0/CryptographicOperationsExtensions.cs b/src/Split/net5.0/CryptographicOperationsExtensions.cs index d65346d8..51393d7b 100644 --- a/src/Split/net5.0/CryptographicOperationsExtensions.cs +++ b/src/Split/net5.0/CryptographicOperationsExtensions.cs @@ -20,16 +20,14 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) { throw new ArgumentNullException(nameof(source)); } - using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source); + return CryptographicOperationsHashData(hashAlgorithm, 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()); + return CryptographicOperationsHashData(hashAlgorithm, source); } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -72,16 +70,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< /// 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; + return CryptographicOperationsTryHashData(hashAlgorithm, source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -131,7 +120,9 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -175,15 +166,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +241,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +302,51 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static byte[] CryptographicOperationsHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.HashData(source), + "SHA1" => SHA1.HashData(source), + "SHA384" => SHA384.HashData(source), + "SHA512" => SHA512.HashData(source), + "MD5" => MD5.HashData(source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return SHA256.TryHashData(source, destination, out bytesWritten); + case "SHA1": + return SHA1.TryHashData(source, destination, out bytesWritten); + case "SHA384": + return SHA384.TryHashData(source, destination, out bytesWritten); + case "SHA512": + return SHA512.TryHashData(source, destination, out bytesWritten); + case "MD5": + return MD5.TryHashData(source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/net6.0/CryptographicOperationsExtensions.cs b/src/Split/net6.0/CryptographicOperationsExtensions.cs index d65346d8..846cff71 100644 --- a/src/Split/net6.0/CryptographicOperationsExtensions.cs +++ b/src/Split/net6.0/CryptographicOperationsExtensions.cs @@ -20,16 +20,14 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) { throw new ArgumentNullException(nameof(source)); } - using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source); + return CryptographicOperationsHashData(hashAlgorithm, 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()); + return CryptographicOperationsHashData(hashAlgorithm, source); } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -72,16 +70,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< /// 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; + return CryptographicOperationsTryHashData(hashAlgorithm, source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -106,8 +95,7 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[ { throw new ArgumentNullException(nameof(source)); } - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); - return hmac.ComputeHash(source); + return CryptographicOperationsHmacData(hashAlgorithm, key, source); } /// /// Computes the HMAC of a stream using the specified key and algorithm. @@ -130,8 +118,7 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + return CryptographicOperationsHmacData(hashAlgorithm, key, source); } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -174,16 +161,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k /// 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; + return CryptographicOperationsTryHmacData(hashAlgorithm, key, source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +236,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +297,83 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static byte[] CryptographicOperationsHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.HashData(source), + "SHA1" => SHA1.HashData(source), + "SHA384" => SHA384.HashData(source), + "SHA512" => SHA512.HashData(source), + "MD5" => MD5.HashData(source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return SHA256.TryHashData(source, destination, out bytesWritten); + case "SHA1": + return SHA1.TryHashData(source, destination, out bytesWritten); + case "SHA384": + return SHA384.TryHashData(source, destination, out bytesWritten); + case "SHA512": + return SHA512.TryHashData(source, destination, out bytesWritten); + case "MD5": + return MD5.TryHashData(source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static byte[] CryptographicOperationsHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => HMACSHA256.HashData(key, source), + "SHA1" => HMACSHA1.HashData(key, source), + "SHA384" => HMACSHA384.HashData(key, source), + "SHA512" => HMACSHA512.HashData(key, source), + "MD5" => HMACMD5.HashData(key, source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return HMACSHA256.TryHashData(key, source, destination, out bytesWritten); + case "SHA1": + return HMACSHA1.TryHashData(key, source, destination, out bytesWritten); + case "SHA384": + return HMACSHA384.TryHashData(key, source, destination, out bytesWritten); + case "SHA512": + return HMACSHA512.TryHashData(key, source, destination, out bytesWritten); + case "MD5": + return HMACMD5.TryHashData(key, source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/net7.0/CryptographicOperationsExtensions.cs b/src/Split/net7.0/CryptographicOperationsExtensions.cs index d65346d8..846cff71 100644 --- a/src/Split/net7.0/CryptographicOperationsExtensions.cs +++ b/src/Split/net7.0/CryptographicOperationsExtensions.cs @@ -20,16 +20,14 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) { throw new ArgumentNullException(nameof(source)); } - using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source); + return CryptographicOperationsHashData(hashAlgorithm, 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()); + return CryptographicOperationsHashData(hashAlgorithm, source); } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -72,16 +70,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< /// 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; + return CryptographicOperationsTryHashData(hashAlgorithm, source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -106,8 +95,7 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[ { throw new ArgumentNullException(nameof(source)); } - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); - return hmac.ComputeHash(source); + return CryptographicOperationsHmacData(hashAlgorithm, key, source); } /// /// Computes the HMAC of a stream using the specified key and algorithm. @@ -130,8 +118,7 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + return CryptographicOperationsHmacData(hashAlgorithm, key, source); } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -174,16 +161,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k /// 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; + return CryptographicOperationsTryHmacData(hashAlgorithm, key, source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +236,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +297,83 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static byte[] CryptographicOperationsHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.HashData(source), + "SHA1" => SHA1.HashData(source), + "SHA384" => SHA384.HashData(source), + "SHA512" => SHA512.HashData(source), + "MD5" => MD5.HashData(source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return SHA256.TryHashData(source, destination, out bytesWritten); + case "SHA1": + return SHA1.TryHashData(source, destination, out bytesWritten); + case "SHA384": + return SHA384.TryHashData(source, destination, out bytesWritten); + case "SHA512": + return SHA512.TryHashData(source, destination, out bytesWritten); + case "MD5": + return MD5.TryHashData(source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static byte[] CryptographicOperationsHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => HMACSHA256.HashData(key, source), + "SHA1" => HMACSHA1.HashData(key, source), + "SHA384" => HMACSHA384.HashData(key, source), + "SHA512" => HMACSHA512.HashData(key, source), + "MD5" => HMACMD5.HashData(key, source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return HMACSHA256.TryHashData(key, source, destination, out bytesWritten); + case "SHA1": + return HMACSHA1.TryHashData(key, source, destination, out bytesWritten); + case "SHA384": + return HMACSHA384.TryHashData(key, source, destination, out bytesWritten); + case "SHA512": + return HMACSHA512.TryHashData(key, source, destination, out bytesWritten); + case "MD5": + return HMACMD5.TryHashData(key, source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/net8.0/CryptographicOperationsExtensions.cs b/src/Split/net8.0/CryptographicOperationsExtensions.cs index d65346d8..846cff71 100644 --- a/src/Split/net8.0/CryptographicOperationsExtensions.cs +++ b/src/Split/net8.0/CryptographicOperationsExtensions.cs @@ -20,16 +20,14 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) { throw new ArgumentNullException(nameof(source)); } - using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source); + return CryptographicOperationsHashData(hashAlgorithm, 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()); + return CryptographicOperationsHashData(hashAlgorithm, source); } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -72,16 +70,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< /// 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; + return CryptographicOperationsTryHashData(hashAlgorithm, source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -106,8 +95,7 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[ { throw new ArgumentNullException(nameof(source)); } - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key); - return hmac.ComputeHash(source); + return CryptographicOperationsHmacData(hashAlgorithm, key, source); } /// /// Computes the HMAC of a stream using the specified key and algorithm. @@ -130,8 +118,7 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + return CryptographicOperationsHmacData(hashAlgorithm, key, source); } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -174,16 +161,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k /// 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; + return CryptographicOperationsTryHmacData(hashAlgorithm, key, source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +236,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +297,83 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static byte[] CryptographicOperationsHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => SHA256.HashData(source), + "SHA1" => SHA1.HashData(source), + "SHA384" => SHA384.HashData(source), + "SHA512" => SHA512.HashData(source), + "MD5" => MD5.HashData(source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return SHA256.TryHashData(source, destination, out bytesWritten); + case "SHA1": + return SHA1.TryHashData(source, destination, out bytesWritten); + case "SHA384": + return SHA384.TryHashData(source, destination, out bytesWritten); + case "SHA512": + return SHA512.TryHashData(source, destination, out bytesWritten); + case "MD5": + return MD5.TryHashData(source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static byte[] CryptographicOperationsHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => HMACSHA256.HashData(key, source), + "SHA1" => HMACSHA1.HashData(key, source), + "SHA384" => HMACSHA384.HashData(key, source), + "SHA512" => HMACSHA512.HashData(key, source), + "MD5" => HMACMD5.HashData(key, source), + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } + static bool CryptographicOperationsTryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, Span destination, out int bytesWritten) + { + var name = hashAlgorithm.Name; + switch (name) + { + case "SHA256": + return HMACSHA256.TryHashData(key, source, destination, out bytesWritten); + case "SHA1": + return HMACSHA1.TryHashData(key, source, destination, out bytesWritten); + case "SHA384": + return HMACSHA384.TryHashData(key, source, destination, out bytesWritten); + case "SHA512": + return HMACSHA512.TryHashData(key, source, destination, out bytesWritten); + case "MD5": + return HMACMD5.TryHashData(key, source, destination, out bytesWritten); + default: + throw new CryptographicException($"'{name}' is not a known hash algorithm."); + } + } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/net9.0/CryptographicOperationsExtensions.cs b/src/Split/net9.0/CryptographicOperationsExtensions.cs index 1d93d09b..52295551 100644 --- a/src/Split/net9.0/CryptographicOperationsExtensions.cs +++ b/src/Split/net9.0/CryptographicOperationsExtensions.cs @@ -63,14 +63,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -111,6 +111,19 @@ public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, b public static ValueTask VerifyHmacAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory key, Stream source, ReadOnlyMemory hash, CancellationToken cancellationToken = default) => new(VerifyHashStreamAsync(CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()), source, hash.ToArray(), cancellationToken)); } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs index d65346d8..e5aa0160 100644 --- a/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs +++ b/src/Split/netcoreapp2.1/CryptographicOperationsExtensions.cs @@ -29,7 +29,9 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) { using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source.ToArray()); + var hash = new byte[hasher.HashSize / 8]; + hasher.TryComputeHash(source, hash, out _); + return hash; } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -73,15 +75,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< 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; + return hasher.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -131,7 +125,9 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -175,15 +171,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +246,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +307,19 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs index d65346d8..e5aa0160 100644 --- a/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs +++ b/src/Split/netcoreapp2.2/CryptographicOperationsExtensions.cs @@ -29,7 +29,9 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) { using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source.ToArray()); + var hash = new byte[hasher.HashSize / 8]; + hasher.TryComputeHash(source, hash, out _); + return hash; } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -73,15 +75,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< 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; + return hasher.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -131,7 +125,9 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -175,15 +171,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +246,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +307,19 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs index d65346d8..e5aa0160 100644 --- a/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs +++ b/src/Split/netcoreapp3.0/CryptographicOperationsExtensions.cs @@ -29,7 +29,9 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) { using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source.ToArray()); + var hash = new byte[hasher.HashSize / 8]; + hasher.TryComputeHash(source, hash, out _); + return hash; } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -73,15 +75,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< 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; + return hasher.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -131,7 +125,9 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -175,15 +171,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +246,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +307,19 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs b/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs index d65346d8..e5aa0160 100644 --- a/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs +++ b/src/Split/netcoreapp3.1/CryptographicOperationsExtensions.cs @@ -29,7 +29,9 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) { using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source.ToArray()); + var hash = new byte[hasher.HashSize / 8]; + hasher.TryComputeHash(source, hash, out _); + return hash; } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -73,15 +75,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< 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; + return hasher.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -131,7 +125,9 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -175,15 +171,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +246,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +307,19 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs b/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs index d65346d8..e5aa0160 100644 --- a/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs +++ b/src/Split/netstandard2.1/CryptographicOperationsExtensions.cs @@ -29,7 +29,9 @@ public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source) public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan source) { using var hasher = CryptographicOperationsCreateHashAlgorithm(hashAlgorithm); - return hasher.ComputeHash(source.ToArray()); + var hash = new byte[hasher.HashSize / 8]; + hasher.TryComputeHash(source, hash, out _); + return hash; } /// /// Computes the hash of data into the provided destination using the specified algorithm. @@ -73,15 +75,7 @@ public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span< 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; + return hasher.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the hash of a stream using the specified algorithm. @@ -131,7 +125,9 @@ public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Strea public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source) { using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - return hmac.ComputeHash(source.ToArray()); + var mac = new byte[hmac.HashSize / 8]; + hmac.TryComputeHash(source, mac, out _); + return mac; } /// /// Computes the HMAC of data into the provided destination using the specified key and algorithm. @@ -175,15 +171,7 @@ public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan k 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; + return hmac.TryComputeHash(source, destination, out bytesWritten); } /// /// Asynchronously computes the HMAC of a stream using the specified key and algorithm. @@ -258,14 +246,14 @@ public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, byte[] key, Strea /// public static bool VerifyHmac(HashAlgorithmName hashAlgorithm, ReadOnlySpan key, ReadOnlySpan source, ReadOnlySpan hash) { - using var hmac = CryptographicOperationsCreateHmac(hashAlgorithm, key.ToArray()); - var hashSizeInBytes = hmac.HashSize / 8; + var hashSizeInBytes = CryptographicOperationsHashSize(hashAlgorithm); 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); + Span mac = stackalloc byte[64]; + CryptographicOperations.TryHmacData(hashAlgorithm, key, source, mac, out var bytesWritten); + return CryptographicOperations.FixedTimeEquals(mac.Slice(0, bytesWritten), hash); } /// /// Determines whether the HMAC of the stream matches , using a comparison whose time does not depend on the contents. @@ -319,6 +307,19 @@ static HashAlgorithm CryptographicOperationsCreateHashAlgorithm(HashAlgorithmNam _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") }; } + static int CryptographicOperationsHashSize(HashAlgorithmName hashAlgorithm) + { + var name = hashAlgorithm.Name; + return name switch + { + "SHA256" => 32, + "SHA1" => 20, + "SHA384" => 48, + "SHA512" => 64, + "MD5" => 16, + _ => throw new CryptographicException($"'{name}' is not a known hash algorithm.") + }; + } static HMAC CryptographicOperationsCreateHmac(HashAlgorithmName hashAlgorithm, byte[] key) { var name = hashAlgorithm.Name; diff --git a/src/Tests/CryptographicOperationsPolyfillTests.cs b/src/Tests/CryptographicOperationsPolyfillTests.cs index ec1cc5fb..5c665edf 100644 --- a/src/Tests/CryptographicOperationsPolyfillTests.cs +++ b/src/Tests/CryptographicOperationsPolyfillTests.cs @@ -275,6 +275,209 @@ public async Task TryHashData_TooShort() await Assert.That(written).IsEqualTo(0); } + static byte[] ReferenceHash(string name, byte[] input) + { + using HashAlgorithm hasher = name switch + { + "MD5" => MD5.Create(), + "SHA1" => SHA1.Create(), + "SHA256" => SHA256.Create(), + "SHA384" => SHA384.Create(), + _ => SHA512.Create() + }; + return hasher.ComputeHash(input); + } + + static byte[] ReferenceMac(string name, byte[] macKey, byte[] input) + { + using HMAC hmac = name switch + { + "MD5" => new HMACMD5(macKey), + "SHA1" => new HMACSHA1(macKey), + "SHA256" => new HMACSHA256(macKey), + "SHA384" => new HMACSHA384(macKey), + _ => new HMACSHA512(macKey) + }; + return hmac.ComputeHash(input); + } + + [Test] + [Arguments("MD5")] + [Arguments("SHA1")] + [Arguments("SHA256")] + [Arguments("SHA384")] + [Arguments("SHA512")] + public async Task AllOverloads_MatchReference(string name) + { + var algorithm = new HashAlgorithmName(name); + var problems = new List(); + + // keys shorter than, equal to and longer than the block size of the algorithm + foreach (var keyLength in new[] {1, 6, 64, 128, 200}) + { + var macKey = new byte[keyLength]; + for (var i = 0; i < macKey.Length; i++) + { + macKey[i] = unchecked((byte) (i * 7 + 3)); + } + + foreach (var length in new[] {0, 1, 55, 56, 64, 111, 112, 128, 1000, 100_000}) + { + // a margin either side of the input, so that the span is not the whole of an array + var buffer = new byte[length + 7]; + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = unchecked((byte) (i * 31 + 7)); + } + + var input = buffer.AsSpan(3, length).ToArray(); + var expectedHash = ReferenceHash(name, input); + var expectedMac = ReferenceMac(name, macKey, input); + var size = expectedHash.Length; + + void Check(string check, bool condition) + { + if (!condition) + { + problems.Add($"{check} key {keyLength} length {length}"); + } + } + + Check("HashData array", CryptographicOperations.HashData(algorithm, input).SequenceEqual(expectedHash)); + Check("HashData span", CryptographicOperations.HashData(algorithm, new ReadOnlySpan(buffer, 3, length)).SequenceEqual(expectedHash)); + + // one spare byte, to check nothing is written past the hash + var destination = new byte[size + 1]; + destination[size] = 0xEE; + var written = CryptographicOperations.HashData(algorithm, new ReadOnlySpan(buffer, 3, length), destination); + Check("HashData destination", written == size && destination.AsSpan(0, size).SequenceEqual(expectedHash) && destination[size] == 0xEE); + + destination = new byte[size]; + var success = CryptographicOperations.TryHashData(algorithm, new ReadOnlySpan(buffer, 3, length), destination, out written); + Check("TryHashData", success && written == size && destination.SequenceEqual(expectedHash)); + + destination = new byte[size - 1]; + success = CryptographicOperations.TryHashData(algorithm, new ReadOnlySpan(buffer, 3, length), destination, out written); + Check("TryHashData too short", !success && written == 0 && destination.All(_ => _ == 0)); + + Check("HmacData array", CryptographicOperations.HmacData(algorithm, macKey, input).SequenceEqual(expectedMac)); + Check("HmacData span", CryptographicOperations.HmacData(algorithm, macKey.AsSpan(), new ReadOnlySpan(buffer, 3, length)).SequenceEqual(expectedMac)); + + destination = new byte[size + 1]; + destination[size] = 0xEE; + written = CryptographicOperations.HmacData(algorithm, macKey.AsSpan(), new ReadOnlySpan(buffer, 3, length), destination); + Check("HmacData destination", written == size && destination.AsSpan(0, size).SequenceEqual(expectedMac) && destination[size] == 0xEE); + + destination = new byte[size]; + success = CryptographicOperations.TryHmacData(algorithm, macKey.AsSpan(), new ReadOnlySpan(buffer, 3, length), destination, out written); + Check("TryHmacData", success && written == size && destination.SequenceEqual(expectedMac)); + + destination = new byte[size - 1]; + success = CryptographicOperations.TryHmacData(algorithm, macKey.AsSpan(), new ReadOnlySpan(buffer, 3, length), destination, out written); + Check("TryHmacData too short", !success && written == 0 && destination.All(_ => _ == 0)); + + Check("VerifyHmac array", CryptographicOperations.VerifyHmac(algorithm, macKey, input, expectedMac)); + Check("VerifyHmac span", CryptographicOperations.VerifyHmac(algorithm, macKey.AsSpan(), new ReadOnlySpan(buffer, 3, length), expectedMac.AsSpan())); + + // a difference in the first and in the last byte + foreach (var index in new[] {0, size - 1}) + { + var tampered = (byte[]) expectedMac.Clone(); + tampered[index] ^= 0x80; + Check("VerifyHmac array tampered", !CryptographicOperations.VerifyHmac(algorithm, macKey, input, tampered)); + Check("VerifyHmac span tampered", !CryptographicOperations.VerifyHmac(algorithm, macKey.AsSpan(), new ReadOnlySpan(buffer, 3, length), tampered.AsSpan())); + } + + for (var i = 0; i < buffer.Length; i++) + { + if (buffer[i] != unchecked((byte) (i * 31 + 7))) + { + problems.Add($"input changed at {i} length {length}"); + break; + } + } + } + } + + await Assert.That(problems).IsEmpty(); + } + + [Test] + public async Task UnknownAlgorithm_Throws() + { + var unknown = new HashAlgorithmName("NOPE"); + var destination = new byte[64]; + + await Assert.That(() => CryptographicOperations.HashData(unknown, data)).Throws(); + await Assert.That(() => CryptographicOperations.HashData(unknown, data.AsSpan())).Throws(); + await Assert.That(() => CryptographicOperations.HashData(unknown, data.AsSpan(), destination)).Throws(); + await Assert.That(() => CryptographicOperations.TryHashData(unknown, data.AsSpan(), destination, out _)).Throws(); + await Assert.That(() => CryptographicOperations.HmacData(unknown, key, data)).Throws(); + await Assert.That(() => CryptographicOperations.HmacData(unknown, key.AsSpan(), data.AsSpan())).Throws(); + await Assert.That(() => CryptographicOperations.HmacData(unknown, key.AsSpan(), data.AsSpan(), destination)).Throws(); + await Assert.That(() => CryptographicOperations.TryHmacData(unknown, key.AsSpan(), data.AsSpan(), destination, out _)).Throws(); + await Assert.That(() => CryptographicOperations.VerifyHmac(unknown, key.AsSpan(), data.AsSpan(), destination.AsSpan())).Throws(); + } + + [Test] + public async Task VerifyHmac_Span_WrongHashLength_Throws() + { + var mac = ExpectedHmacSha256(); + await Assert.That(() => CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), mac.AsSpan(0, 31))).Throws(); + await Assert.That(() => CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), new byte[33].AsSpan())).Throws(); + await Assert.That(() => CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key, data, new byte[31])).Throws(); + } + + [Test] + public async Task HmacData_DestinationTooShort_Throws() + { + var exception = await Assert.That(() => + { + var destination = new byte[31]; + CryptographicOperations.HmacData(HashAlgorithmName.SHA256, key.AsSpan(), data.AsSpan(), destination); + }) + .Throws(); + await Assert.That(exception!.Message.StartsWith("Destination is too short.")).IsTrue(); + } + + [Test] + public async Task HashAndHmac_Concurrent() + { + var input = new byte[5000]; + for (var i = 0; i < input.Length; i++) + { + input[i] = unchecked((byte) (i * 13)); + } + + var expectedHash = ReferenceHash("SHA256", input); + var expectedMac = ReferenceMac("SHA256", key, input); + + var tasks = new List>(); + for (var t = 0; t < 4; t++) + { + tasks.Add(Task.Run(() => + { + var destination = new byte[32]; + for (var i = 0; i < 500; i++) + { + if (!CryptographicOperations.TryHashData(HashAlgorithmName.SHA256, input.AsSpan(), destination, out _) || + !destination.SequenceEqual(expectedHash) || + !CryptographicOperations.TryHmacData(HashAlgorithmName.SHA256, key.AsSpan(), input.AsSpan(), destination, out _) || + !destination.SequenceEqual(expectedMac) || + !CryptographicOperations.VerifyHmac(HashAlgorithmName.SHA256, key.AsSpan(), input.AsSpan(), expectedMac.AsSpan())) + { + return false; + } + } + + return true; + })); + } + + var results = await Task.WhenAll(tasks); + await Assert.That(results.All(_ => _)).IsTrue(); + } + [Test] public async Task HmacData_Span() {