File: System\Security\Cryptography\BasicSymmetricCipherLiteBCrypt.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 Internal.NativeCrypto;

namespace System.Security.Cryptography
{
    internal sealed class BasicSymmetricCipherLiteBCrypt : ILiteSymmetricCipher
    {
        private readonly bool _encrypting;
        private readonly byte[]? _currentIv;
        private SafeKeyHandle _hKey;

        public int BlockSizeInBytes { get; }
        public int PaddingSizeInBytes { get; }

        public BasicSymmetricCipherLiteBCrypt(
            SafeAlgorithmHandle algorithm,
            int blockSizeInBytes,
            int paddingSizeInBytes,
            ReadOnlySpan<byte> key,
            bool ownsParentHandle,
            ReadOnlySpan<byte> iv,
            bool encrypting)
        {
            if (!iv.IsEmpty)
            {
                // Must copy the input IV
                _currentIv = iv.ToArray();
            }

            BlockSizeInBytes = blockSizeInBytes;
            PaddingSizeInBytes = paddingSizeInBytes;
            _encrypting = encrypting;
            _hKey = Interop.BCrypt.BCryptImportKey(algorithm, key);

            if (ownsParentHandle)
            {
                _hKey.SetParentHandle(algorithm);
            }
        }

        public int Transform(ReadOnlySpan<byte> input, Span<byte> output)
        {
            Debug.Assert(input.Length > 0);
            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

            int numBytesWritten = 0;

            // BCryptEncrypt and BCryptDecrypt can do in place encryption, but if the buffers overlap
            // the offset must be zero. In that case, we need to copy to a temporary location.
            if (input.Overlaps(output, out int offset) && offset != 0)
            {
                byte[] rented = CryptoPool.Rent(output.Length);

                try
                {
                    numBytesWritten = BCryptTransform(input, rented);
                    rented.AsSpan(0, numBytesWritten).CopyTo(output);
                }
                finally
                {
                    CryptoPool.Return(rented, clearSize: numBytesWritten);
                }
            }
            else
            {
                numBytesWritten = BCryptTransform(input, output);
            }

            if (numBytesWritten != input.Length)
            {
                // CNG gives us no way to tell BCryptDecrypt() that we're decrypting the final block, nor is it performing any
                // padding /depadding for us. So there's no excuse for a provider to hold back output for "future calls." Though
                // this isn't technically our problem to detect, we might as well detect it now for easier diagnosis.
                throw new CryptographicException(SR.Cryptography_UnexpectedTransformTruncation);
            }

            return numBytesWritten;

            int BCryptTransform(ReadOnlySpan<byte> input, Span<byte> output)
            {
                return _encrypting ?
                    Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output) :
                    Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
            }
        }

        public void Reset(ReadOnlySpan<byte> iv)
        {
            if (_currentIv is not null)
            {
                iv.CopyTo(_currentIv);
            }
        }

        public int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
        {
            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

            int numBytesWritten = 0;

            if (input.Length != 0)
            {
                numBytesWritten = Transform(input, output);
                Debug.Assert(numBytesWritten == input.Length); // Our implementation of Transform() guarantees this. See comment above.
            }

            return numBytesWritten;
        }

        public void Dispose()
        {
            if (_currentIv is not null)
            {
                CryptographicOperations.ZeroMemory(_currentIv);
            }

            _hKey?.Dispose();
            _hKey = null!;
        }

    }
}