File: System\Security\Cryptography\CryptographicOperations.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Internal.Cryptography;
 
namespace System.Security.Cryptography
{
    public static class CryptographicOperations
    {
        /// <summary>
        /// Determine the equality of two byte sequences in an amount of time which depends on
        /// the length of the sequences, but not the values.
        /// </summary>
        /// <param name="left">The first buffer to compare.</param>
        /// <param name="right">The second buffer to compare.</param>
        /// <returns>
        ///   <c>true</c> if <paramref name="left"/> and <paramref name="right"/> have the same
        ///   values for <see cref="ReadOnlySpan{T}.Length"/> and the same contents, <c>false</c>
        ///   otherwise.
        /// </returns>
        /// <remarks>
        ///   This method compares two buffers' contents for equality in a manner which does not
        ///   leak timing information, making it ideal for use within cryptographic routines.
        ///   This method will short-circuit and return <c>false</c> only if <paramref name="left"/>
        ///   and <paramref name="right"/> have different lengths.
        ///
        ///   Fixed-time behavior is guaranteed in all other cases, including if <paramref name="left"/>
        ///   and <paramref name="right"/> reference the same address.
        /// </remarks>
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        public static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
        {
            // NoOptimization because we want this method to be exactly as non-short-circuiting
            // as written.
            //
            // NoInlining because the NoOptimization would get lost if the method got inlined.
 
            if (left.Length != right.Length)
            {
                return false;
            }
 
            int length = left.Length;
            int accum = 0;
 
            for (int i = 0; i < length; i++)
            {
                accum |= left[i] - right[i];
            }
 
            return accum == 0;
        }
 
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        public static void ZeroMemory(Span<byte> buffer)
        {
            // NoOptimize to prevent the optimizer from deciding this call is unnecessary
            // NoInlining to prevent the inliner from forgetting that the method was no-optimize
            buffer.Clear();
        }
 
        /// <summary>
        /// Computes the hash of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The data to hash.</param>
        /// <returns>The hash of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source)
        {
            ArgumentNullException.ThrowIfNull(source);
            return HashData(hashAlgorithm, new ReadOnlySpan<byte>(source));
        }
 
        /// <summary>
        /// Computes the hash of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The data to hash.</param>
        /// <returns>The hash of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///   <see langword="null" />.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            Debug.Assert(hashAlgorithm.Name is not null);
 
            byte[] buffer = new byte[hashSizeInBytes];
            int written = HashProviderDispenser.OneShotHashProvider.HashData(hashAlgorithm.Name, source, buffer);
            Debug.Assert(written == hashSizeInBytes);
            return buffer;
        }
 
        /// <summary>
        /// Computes the hash of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The data to hash.</param>
        /// <param name="destination">The buffer to receive the hash value.</param>
        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
        /// <exception cref="ArgumentException">
        ///   <para>The buffer in <paramref name="destination"/> is too small to hold the calculated hash size.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///   <see langword="null" />.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination)
        {
            if (!TryHashData(hashAlgorithm, source, destination, out int bytesWritten))
            {
                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
            }
 
            return bytesWritten;
        }
 
        /// <summary>
        /// Attempts to compute the hash of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The data to hash.</param>
        /// <param name="destination">The buffer to receive the hash value.</param>
        /// <param name="bytesWritten">
        ///   When this method returns, the total number of bytes written into <paramref name="destination"/>.
        /// </param>
        /// <returns>
        ///   <see langword="false"/> if <paramref name="destination"/> is too small to hold the
        ///   calculated hash, <see langword="true"/> otherwise.
        /// </returns>
        /// <exception cref="ArgumentException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///   <see langword="null" />.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            Debug.Assert(hashAlgorithm.Name is not null);
 
            if (destination.Length < hashSizeInBytes)
            {
                bytesWritten = 0;
                return false;
            }
 
            bytesWritten = HashProviderDispenser.OneShotHashProvider.HashData(hashAlgorithm.Name, source, destination);
            Debug.Assert(bytesWritten == hashSizeInBytes);
            return true;
        }
 
        /// <summary>
        /// Computes the hash of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The stream to hash.</param>
        /// <returns>The hash of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
            return LiteHashProvider.HashStream(hashAlgorithm.Name, hashSizeInBytes, source);
        }
 
        /// <summary>
        /// Computes the hash of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The stream to hash.</param>
        /// <param name="destination">The buffer to receive the hash value.</param>
        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>The buffer in <paramref name="destination"/> is too small to hold the calculated hash size.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span<byte> destination)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
            CheckDestinationSize(hashSizeInBytes, destination.Length);
 
            int written = LiteHashProvider.HashStream(hashAlgorithm.Name, source, destination);
            Debug.Assert(written == hashSizeInBytes);
            return written;
        }
 
        /// <summary>
        /// Asynchronously computes the hash of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The stream to hash.</param>
        /// <param name="destination">The buffer to receive the hash value.</param>
        /// <param name="cancellationToken">
        ///   The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.
        /// </param>
        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>The buffer in <paramref name="destination"/> is too small to hold the calculated hash size.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///   <paramref name="cancellationToken"/> has been canceled.
        /// </exception>
        public static ValueTask<int> HashDataAsync(
            HashAlgorithmName hashAlgorithm,
            Stream source,
            Memory<byte> destination,
            CancellationToken cancellationToken = default)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
            CheckDestinationSize(hashSizeInBytes, destination.Length);
 
            return LiteHashProvider.HashStreamAsync(
                hashAlgorithm.Name,
                source,
                destination,
                cancellationToken);
        }
 
        /// <summary>
        /// Asynchronously computes the hash of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the hash.</param>
        /// <param name="source">The stream to hash.</param>
        /// <param name="cancellationToken">
        ///   The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.
        /// </param>
        /// <returns>The hash of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///   <paramref name="cancellationToken"/> has been canceled.
        /// </exception>
        public static ValueTask<byte[]> HashDataAsync(
            HashAlgorithmName hashAlgorithm,
            Stream source,
            CancellationToken cancellationToken = default)
        {
            CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
 
            return LiteHashProvider.HashStreamAsync(hashAlgorithm.Name, source, cancellationToken);
        }
 
        /// <summary>
        /// Computes the HMAC of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <returns>The HMAC of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source)
        {
            ArgumentNullException.ThrowIfNull(key);
            ArgumentNullException.ThrowIfNull(source);
            return HmacData(hashAlgorithm, new ReadOnlySpan<byte>(key), new ReadOnlySpan<byte>(source));
        }
 
        /// <summary>
        /// Computes the HMAC of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <returns>The HMAC of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///   <see langword="null" />.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            Debug.Assert(hashAlgorithm.Name is not null);
            byte[] buffer = new byte[hashSizeInBytes];
 
            int written = HashProviderDispenser.OneShotHashProvider.MacData(hashAlgorithm.Name, key, source, buffer);
            Debug.Assert(written == hashSizeInBytes);
            return buffer;
        }
 
        /// <summary>
        /// Computes the HMAC of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <param name="destination">The buffer to receive the HMAC value.</param>
        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
        /// <exception cref="ArgumentException">
        ///   <para>The buffer in <paramref name="destination"/> is too small to hold the calculated hash size.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///   <see langword="null" />.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static int HmacData(
            HashAlgorithmName hashAlgorithm,
            ReadOnlySpan<byte> key,
            ReadOnlySpan<byte> source,
            Span<byte> destination)
        {
            if (!TryHmacData(hashAlgorithm, key, source, destination, out int bytesWritten))
            {
                throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
            }
 
            return bytesWritten;
        }
 
        /// <summary>
        /// Attempts to compute the HMAC of data.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <param name="destination">The buffer to receive the HMAC value.</param>
        /// <param name="bytesWritten">
        ///   When this method returns, the total number of bytes written into <paramref name="destination"/>.
        /// </param>
        /// <returns>
        ///   <see langword="false"/> if <paramref name="destination"/> is too small to hold the
        ///   calculated HMAC, <see langword="true"/> otherwise.
        /// </returns>
        /// <exception cref="ArgumentException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///   <see langword="null" />.
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static bool TryHmacData(
            HashAlgorithmName hashAlgorithm,
            ReadOnlySpan<byte> key,
            ReadOnlySpan<byte> source,
            Span<byte> destination,
            out int bytesWritten)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            Debug.Assert(hashAlgorithm.Name is not null);
 
            if (destination.Length < hashSizeInBytes)
            {
                bytesWritten = 0;
                return false;
            }
 
            bytesWritten = HashProviderDispenser.OneShotHashProvider.MacData(hashAlgorithm.Name, key, source, destination);
            Debug.Assert(bytesWritten == hashSizeInBytes);
            return true;
        }
 
        /// <summary>
        /// Computes the HMAC of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <returns>The HMAC of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source)
        {
            ArgumentNullException.ThrowIfNull(key);
            return HmacData(hashAlgorithm, new ReadOnlySpan<byte>(key), source);
        }
 
        /// <summary>
        /// Computes the HMAC of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <returns>The HMAC of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
 
            return LiteHashProvider.HmacStream(hashAlgorithm.Name, hashSizeInBytes, key, source);
        }
 
        /// <summary>
        /// Computes the HMAC of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The data to compute the HMAC over.</param>
        /// <param name="destination">The buffer to receive the HMAC value.</param>
        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        ///   <para>-or-</para>
        ///   <para>The buffer in <paramref name="destination"/> is too small to hold the calculated HMAC size.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source, Span<byte> destination)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
            CheckDestinationSize(hashSizeInBytes, destination.Length);
 
            return LiteHashProvider.HmacStream(hashAlgorithm.Name, key, source, destination);
        }
 
        /// <summary>
        /// Asynchronously computes the HMAC of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The stream to compute the HMAC over.</param>
        /// <param name="cancellationToken">
        ///   The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.
        /// </param>
        /// <returns>The HMAC of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="key" /> or <paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///   <paramref name="cancellationToken"/> has been canceled.
        /// </exception>
        public static ValueTask<byte[]> HmacDataAsync(
            HashAlgorithmName hashAlgorithm,
            byte[] key,
            Stream source,
            CancellationToken cancellationToken = default)
        {
            ArgumentNullException.ThrowIfNull(key);
            return HmacDataAsync(hashAlgorithm, new ReadOnlyMemory<byte>(key), source, cancellationToken);
        }
 
        /// <summary>
        /// Asynchronously computes the HMAC of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The stream to compute the HMAC over.</param>
        /// <param name="destination">The buffer to receive the HMAC value.</param>
        /// <param name="cancellationToken">
        ///   The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.
        /// </param>
        /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>The buffer in <paramref name="destination"/> is too small to hold the calculated HMAC size.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///   <paramref name="cancellationToken"/> has been canceled.
        /// </exception>
        public static ValueTask<int> HmacDataAsync(
            HashAlgorithmName hashAlgorithm,
            ReadOnlyMemory<byte> key,
            Stream source,
            Memory<byte> destination,
            CancellationToken cancellationToken = default)
        {
            int hashSizeInBytes = CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
            CheckDestinationSize(hashSizeInBytes, destination.Length);
 
            return LiteHashProvider.HmacStreamAsync(hashAlgorithm.Name, key.Span, source, destination, cancellationToken);
        }
 
        /// <summary>
        /// Asynchronously computes the HMAC of a stream.
        /// </summary>
        /// <param name="hashAlgorithm">The algorithm used to compute the HMAC.</param>
        /// <param name="key">The secret key. The key can be any length.</param>
        /// <param name="source">The stream to compute the HMAC over.</param>
        /// <param name="cancellationToken">
        ///   The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.
        /// </param>
        /// <returns>The HMAC of the data.</returns>
        /// <exception cref="ArgumentNullException">
        ///   <para><paramref name="source" /> is <see langword="null" />.</para>
        ///   <para>-or-</para>
        ///   <para>
        ///     <paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is
        ///     <see langword="null" />.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para><paramref name="hashAlgorithm"/> has a <see cref="HashAlgorithmName.Name" /> that is empty.</para>
        ///   <para>-or-</para>
        ///   <para><paramref name="source" /> does not support reading.</para>
        /// </exception>
        /// <exception cref="PlatformNotSupportedException">
        ///   <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
        /// </exception>
        /// <exception cref="OperationCanceledException">
        ///   <paramref name="cancellationToken"/> has been canceled.
        /// </exception>
        public static ValueTask<byte[]> HmacDataAsync(
            HashAlgorithmName hashAlgorithm,
            ReadOnlyMemory<byte> key,
            Stream source,
            CancellationToken cancellationToken = default)
        {
            CheckHashAndGetLength(hashAlgorithm);
            CheckStream(source);
            Debug.Assert(hashAlgorithm.Name is not null);
 
            return LiteHashProvider.HmacStreamAsync(hashAlgorithm.Name, key.Span, source, cancellationToken);
        }
 
        private static void CheckStream([NotNull] Stream source)
        {
            ArgumentNullException.ThrowIfNull(source);
 
            if (!source.CanRead)
            {
                throw new ArgumentException(SR.Argument_StreamNotReadable, nameof(source));
            }
        }
 
        // Currently this is accurate for HMAC algorithms, too, so we can re-use it.
        // If there is ever an HMAC / hash algorithm that diverge, that needs to be handled separately.
        private static int CheckHashAndGetLength(HashAlgorithmName hashAlgorithm)
        {
            ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
 
            switch (hashAlgorithm.Name)
            {
                case HashAlgorithmNames.SHA256:
                    return SHA256.HashSizeInBytes;
                case HashAlgorithmNames.SHA1:
                    return SHA1.HashSizeInBytes;
                case HashAlgorithmNames.SHA512:
                    return SHA512.HashSizeInBytes;
                case HashAlgorithmNames.SHA384:
                    return SHA384.HashSizeInBytes;
                case HashAlgorithmNames.SHA3_256:
                    if (!HashProviderDispenser.HashSupported(HashAlgorithmNames.SHA3_256))
                    {
                        throw new PlatformNotSupportedException();
                    }
 
                    return SHA3_256.HashSizeInBytes;
                case HashAlgorithmNames.SHA3_384:
                    if (!HashProviderDispenser.HashSupported(HashAlgorithmNames.SHA3_384))
                    {
                        throw new PlatformNotSupportedException();
                    }
 
                    return SHA3_384.HashSizeInBytes;
                case HashAlgorithmNames.SHA3_512:
                    if (!HashProviderDispenser.HashSupported(HashAlgorithmNames.SHA3_512))
                    {
                        throw new PlatformNotSupportedException();
                    }
 
                    return SHA3_512.HashSizeInBytes;
                case HashAlgorithmNames.MD5 when Helpers.HasMD5:
                    return MD5.HashSizeInBytes;
                default:
                    throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithm.Name));
            }
        }
 
        private static void CheckDestinationSize(int requiredSize, int destinationSize)
        {
            if (destinationSize < requiredSize)
            {
                throw new ArgumentException(SR.Argument_DestinationTooShort, "destination");
            }
        }
    }
}