File: System\Security\Cryptography\HashProviderCng.cs
Web Access
Project: src\src\runtime\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;
using System.Diagnostics;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using Internal.Cryptography;
using BCryptCreateHashFlags = Interop.BCrypt.BCryptCreateHashFlags;
using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags;
using NTSTATUS = Interop.BCrypt.NTSTATUS;

namespace System.Security.Cryptography
{
    //
    // Provides hash services via the native provider (CNG).
    //
    internal sealed class HashProviderCng : HashProvider
    {
        //
        //   - "hashAlgId" must be a name recognized by BCryptOpenAlgorithmProvider(). Examples: MD5, SHA1, SHA256.
        //
        //   - "key" activates MAC hashing if present. If null, this HashProvider performs a regular old hash.
        //
        public HashProviderCng(string hashAlgId, byte[]? key) : this(hashAlgId, key, isHmac: key != null)
        {
        }

        internal HashProviderCng(string hashAlgId, ReadOnlySpan<byte> key, bool isHmac)
        {
            BCryptOpenAlgorithmProviderFlags dwFlags = BCryptOpenAlgorithmProviderFlags.None;
            if (isHmac)
            {
                _key = key.ToArray();
                dwFlags |= BCryptOpenAlgorithmProviderFlags.BCRYPT_ALG_HANDLE_HMAC_FLAG;
            }

            _hAlgorithm = Interop.BCrypt.BCryptAlgorithmCache.GetCachedBCryptAlgorithmHandle(hashAlgId, dwFlags, out _hashSize);
            NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out _hHash, IntPtr.Zero, 0, key, key.Length, BCryptCreateHashFlags.BCRYPT_HASH_REUSABLE_FLAG);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                _hHash.Dispose();
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }
        }

        private HashProviderCng(
            SafeBCryptAlgorithmHandle algorithmHandle,
            SafeBCryptHashHandle hashHandle,
            byte[]? key,
            int hashSize,
            bool running)
        {
            _hAlgorithm = algorithmHandle;
            _hHash = hashHandle;
            _key = key.CloneByteArray();
            _hashSize = hashSize;
            _running = running;
        }

        public sealed override void AppendHashData(ReadOnlySpan<byte> source)
        {
            Debug.Assert(_hHash != null);

            using (ConcurrencyBlock.Enter(ref _block))
            {
                NTSTATUS ntStatus = Interop.BCrypt.BCryptHashData(_hHash, source, source.Length, 0);
                if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                {
                    throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                }

                _running = true;
            }
        }

        public override int FinalizeHashAndReset(Span<byte> destination)
        {
            Debug.Assert(destination.Length >= _hashSize);
            Debug.Assert(_hHash != null);

            using (ConcurrencyBlock.Enter(ref _block))
            {
                NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(_hHash, destination, _hashSize, 0);

                if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                {
                    throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                }

                _running = false;
                Reset();
                return _hashSize;
            }
        }

        public override int GetCurrentHash(Span<byte> destination)
        {
            Debug.Assert(destination.Length >= _hashSize);
            Debug.Assert(_hHash != null);

            using (ConcurrencyBlock.Enter(ref _block))
            {
                using (SafeBCryptHashHandle tmpHash = Interop.BCrypt.BCryptDuplicateHash(_hHash))
                {
                    NTSTATUS ntStatus = Interop.BCrypt.BCryptFinishHash(tmpHash, destination, _hashSize, 0);

                    if (ntStatus != NTSTATUS.STATUS_SUCCESS)
                    {
                        throw Interop.BCrypt.CreateCryptographicException(ntStatus);
                    }

                    return _hashSize;
                }
            }
        }

        public override HashProviderCng Clone()
        {
            using (ConcurrencyBlock.Enter(ref _block))
            {
                SafeBCryptHashHandle clone = Interop.BCrypt.BCryptDuplicateHash(_hHash);
                return new HashProviderCng(_hAlgorithm, clone, _key, _hashSize, _running);
            }
        }

        public sealed override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Not disposing of _hAlgorithm as we got this from a cache. So it's not ours to Dispose().
                _hHash.Dispose();

                if (_key != null)
                {
                    byte[] key = _key;
                    _key = null;
                    Array.Clear(key);
                }
            }
        }

        public sealed override int HashSizeInBytes => _hashSize;

        public override void Reset()
        {
            // Reset does not need to use ConcurrencyBlock. It either no-ops, or creates an entirely new handle, exchanges
            // them, and disposes of the old handle. We don't need to block concurrency on the Dispose because SafeHandle
            // does that.
            if (!_running)
            {
                return;
            }

            const BCryptCreateHashFlags Flags = BCryptCreateHashFlags.BCRYPT_HASH_REUSABLE_FLAG;

            SafeBCryptHashHandle hHash;
            NTSTATUS ntStatus = Interop.BCrypt.BCryptCreateHash(_hAlgorithm, out hHash, IntPtr.Zero, 0, _key, _key == null ? 0 : _key.Length, Flags);

            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
            {
                hHash.Dispose();
                throw Interop.BCrypt.CreateCryptographicException(ntStatus);
            }

            SafeBCryptHashHandle? previousHash = Interlocked.Exchange(ref _hHash, hHash);
            previousHash?.Dispose();
        }

        private readonly SafeBCryptAlgorithmHandle _hAlgorithm;
        private SafeBCryptHashHandle _hHash;
        private byte[]? _key;

        private readonly int _hashSize;
        private bool _running;
        private ConcurrencyBlock _block;
    }
}