File: System\Security\Cryptography\HashAlgorithm.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.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
 
namespace System.Security.Cryptography
{
    public abstract class HashAlgorithm : IDisposable, ICryptoTransform
    {
        private bool _disposed;
        protected int HashSizeValue;
        protected internal byte[]? HashValue;
        protected int State;
 
        protected HashAlgorithm() { }
 
        [Obsolete(Obsoletions.DefaultCryptoAlgorithmsMessage, DiagnosticId = Obsoletions.DefaultCryptoAlgorithmsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public static HashAlgorithm Create() =>
            CryptoConfigForwarder.CreateDefaultHashAlgorithm();
 
        [Obsolete(Obsoletions.CryptoStringFactoryMessage, DiagnosticId = Obsoletions.CryptoStringFactoryDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        [RequiresUnreferencedCode(CryptoConfigForwarder.CreateFromNameUnreferencedCodeMessage)]
        public static HashAlgorithm? Create(string hashName) =>
            CryptoConfigForwarder.CreateFromName<HashAlgorithm>(hashName);
 
        public virtual int HashSize => HashSizeValue;
 
        public virtual byte[]? Hash
        {
            get
            {
                ObjectDisposedException.ThrowIf(_disposed, this);
                if (State != 0)
                    throw new CryptographicUnexpectedOperationException(SR.Cryptography_HashNotYetFinalized);
 
                return (byte[]?)HashValue?.Clone();
            }
        }
 
        public byte[] ComputeHash(byte[] buffer)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
            ArgumentNullException.ThrowIfNull(buffer);
 
            HashCore(buffer, 0, buffer.Length);
            return CaptureHashCodeAndReinitialize();
        }
 
        public bool TryComputeHash(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
 
            if (destination.Length < HashSizeValue / 8)
            {
                bytesWritten = 0;
                return false;
            }
 
            HashCore(source);
            if (!TryHashFinal(destination, out bytesWritten))
            {
                // The only reason for failure should be that the destination isn't long enough,
                // but we checked the size earlier.
                throw new InvalidOperationException(SR.InvalidOperation_IncorrectImplementation);
            }
            HashValue = null;
 
            Initialize();
            return true;
        }
 
        public byte[] ComputeHash(byte[] buffer, int offset, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
 
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            if (count < 0 || (count > buffer.Length))
                throw new ArgumentException(SR.Argument_InvalidValue);
            if ((buffer.Length - count) < offset)
                throw new ArgumentException(SR.Argument_InvalidOffLen);
 
            ObjectDisposedException.ThrowIf(_disposed, this);
 
            HashCore(buffer, offset, count);
            return CaptureHashCodeAndReinitialize();
        }
 
        public byte[] ComputeHash(Stream inputStream)
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
 
            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
            byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
 
            int bytesRead;
            int clearLimit = 0;
 
            while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                if (bytesRead > clearLimit)
                {
                    clearLimit = bytesRead;
                }
 
                HashCore(buffer, 0, bytesRead);
            }
 
            CryptographicOperations.ZeroMemory(buffer.AsSpan(0, clearLimit));
            ArrayPool<byte>.Shared.Return(buffer, clearArray: false);
            return CaptureHashCodeAndReinitialize();
        }
 
        public Task<byte[]> ComputeHashAsync(
            Stream inputStream,
            CancellationToken cancellationToken = default)
        {
            ArgumentNullException.ThrowIfNull(inputStream);
 
            ObjectDisposedException.ThrowIf(_disposed, this);
 
            return ComputeHashAsyncCore(inputStream, cancellationToken);
        }
 
        private async Task<byte[]> ComputeHashAsyncCore(
            Stream inputStream,
            CancellationToken cancellationToken)
        {
            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
            byte[] rented = ArrayPool<byte>.Shared.Rent(4096);
            Memory<byte> buffer = rented;
            int clearLimit = 0;
            int bytesRead;
 
            while ((bytesRead = await inputStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0)
            {
                if (bytesRead > clearLimit)
                {
                    clearLimit = bytesRead;
                }
 
                HashCore(rented, 0, bytesRead);
            }
 
            CryptographicOperations.ZeroMemory(rented.AsSpan(0, clearLimit));
            ArrayPool<byte>.Shared.Return(rented, clearArray: false);
            return CaptureHashCodeAndReinitialize();
        }
 
        private byte[] CaptureHashCodeAndReinitialize()
        {
            HashValue = HashFinal();
 
            // Clone the hash value prior to invoking Initialize in case the user-defined Initialize
            // manipulates the array.
            byte[] tmp = (byte[])HashValue.Clone();
            Initialize();
            return tmp;
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        public void Clear()
        {
            (this as IDisposable).Dispose();
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Although we don't have any resources to dispose at this level,
                // we need to continue to throw ObjectDisposedExceptions from CalculateHash
                // for compatibility with the .NET Framework.
                _disposed = true;
            }
            return;
        }
 
        // ICryptoTransform methods
 
        // We assume any HashAlgorithm can take input a byte at a time
        public virtual int InputBlockSize => 1;
        public virtual int OutputBlockSize => 1;
        public virtual bool CanTransformMultipleBlocks => true;
        public virtual bool CanReuseTransform => true;
 
        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[]? outputBuffer, int outputOffset)
        {
            ValidateTransformBlock(inputBuffer, inputOffset, inputCount);
 
            // Change the State value
            State = 1;
 
            HashCore(inputBuffer, inputOffset, inputCount);
            if ((outputBuffer != null) && ((inputBuffer != outputBuffer) || (inputOffset != outputOffset)))
            {
                // We let BlockCopy do the destination array validation
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
            }
            return inputCount;
        }
 
        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            ValidateTransformBlock(inputBuffer, inputOffset, inputCount);
 
            HashCore(inputBuffer, inputOffset, inputCount);
            HashValue = CaptureHashCodeAndReinitialize();
            byte[] outputBytes;
            if (inputCount != 0)
            {
                outputBytes = new byte[inputCount];
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBytes, 0, inputCount);
            }
            else
            {
                outputBytes = Array.Empty<byte>();
            }
 
            // Reset the State value
            State = 0;
 
            return outputBytes;
        }
 
        private void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            ArgumentNullException.ThrowIfNull(inputBuffer);
 
            ArgumentOutOfRangeException.ThrowIfNegative(inputOffset);
            if (inputCount < 0 || inputCount > inputBuffer.Length)
                throw new ArgumentException(SR.Argument_InvalidValue);
            if ((inputBuffer.Length - inputCount) < inputOffset)
                throw new ArgumentException(SR.Argument_InvalidOffLen);
 
            ObjectDisposedException.ThrowIf(_disposed, this);
        }
 
        protected abstract void HashCore(byte[] array, int ibStart, int cbSize);
        protected abstract byte[] HashFinal();
        public abstract void Initialize();
 
        protected virtual void HashCore(ReadOnlySpan<byte> source)
        {
            // Use ArrayPool.Shared instead of CryptoPool because the array is passed out.
            byte[] array = ArrayPool<byte>.Shared.Rent(source.Length);
            source.CopyTo(array);
            HashCore(array, 0, source.Length);
            Array.Clear(array, 0, source.Length);
            ArrayPool<byte>.Shared.Return(array);
        }
 
        protected virtual bool TryHashFinal(Span<byte> destination, out int bytesWritten)
        {
            int hashSizeInBytes = HashSizeValue / 8;
 
            if (destination.Length >= hashSizeInBytes)
            {
                byte[] final = HashFinal();
                if (final.Length == hashSizeInBytes)
                {
                    new ReadOnlySpan<byte>(final).CopyTo(destination);
                    bytesWritten = final.Length;
                    return true;
                }
 
                throw new InvalidOperationException(SR.InvalidOperation_IncorrectImplementation);
            }
 
            bytesWritten = 0;
            return false;
        }
    }
}