Skip to main content

C# symetric async encrypt/decrypt class.

namespace Utils
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Threading.Tasks;

    /// <summary>
    /// Async encrypt/decrypt class.
    /// </summary>
    /// <remarks>https://github.com/IntelliTect/EssentialCSharp/blob/v8.0/src/Shared/Cryptographer.cs</remarks>
    public class Encryptor : IDisposable
    {
        /// <inheritdoc cref="SymmetricAlgorithm" />
        public SymmetricAlgorithm CryptoAlgorithm { get; }

        public Encryptor(SymmetricAlgorithm cryptoAlgorithm)
        {
            CryptoAlgorithm = cryptoAlgorithm;
        }

        public Encryptor()
            : this(Aes.Create()) { }

        public byte[] Encrypt(string text)
        {
            return EncryptAsync(text).Result;
        }

        public async Task<byte[]> EncryptAsync(string text)
        {
            return await EncryptAsync(text, CryptoAlgorithm.CreateEncryptor());
        }

        public async Task<byte[]> EncryptAsync(string text, Stream outputFileStream)
        {
            return await EncryptAsync(text, CryptoAlgorithm.CreateEncryptor(), outputFileStream);
        }

        public static async Task<byte[]> EncryptAsync(string plainText, byte[] key, byte[] iv)
        {
            byte[] encrypted;

            using (var aes = new AesManaged())
            {
                var encryptor = aes.CreateEncryptor(key, iv);

                encrypted = await EncryptAsync(plainText, encryptor);
            }

            return encrypted;
        }

        private static async Task<byte[]> EncryptAsync(string plainText, ICryptoTransform encryptor)
        {
            using var memoryStream = new MemoryStream();
            return await EncryptAsync(plainText, encryptor, memoryStream);
        }

        private static async Task<byte[]> EncryptAsync(
            string plainText,
            ICryptoTransform encryptor,
            Stream outputStream
        )
        {
            byte[] encrypted;
            MemoryStream memoryStream;

            // Create crypto stream using the CryptoStream class. This class is the key to encryption
            // and encrypts and decrypts data from any given stream. In this case, we will pass a memory stream
            // to encrypt
            using (var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
            {
                // Create StreamWriter and write data to a stream
                using (var streamWriter = new StreamWriter(cryptoStream))
                {
                    await streamWriter.WriteAsync(plainText);
                    if (outputStream is MemoryStream stream)
                    {
                        streamWriter.Close();
                        memoryStream = stream;
                    }
                    else
                    {
                        using (memoryStream = new MemoryStream())
                        {
                            await outputStream.CopyToAsync(memoryStream);
                        }
                    }

                    encrypted = memoryStream.ToArray();
                }
            }

            return encrypted;
        }

        public string Decrypt(byte[] encryptedData)
        {
            return DecryptAsync(encryptedData).Result;
        }

        public async Task<string> DecryptAsync(byte[] encryptedData)
        {
            return await DecryptAsync(encryptedData, CryptoAlgorithm.CreateDecryptor());
        }

        public async Task DecryptAsync(byte[] encryptedData, Stream outputStream)
        {
            await DecryptAsync(encryptedData, CryptoAlgorithm.CreateDecryptor(), outputStream);
        }

        public static async Task<string> DecryptAsync(byte[] encryptedData, byte[] key, byte[] iV)
        {
            using (var aes = new AesManaged())
            {
                var decryptor = aes.CreateDecryptor(key, iV);
                var plaintext = await DecryptAsync(encryptedData, decryptor);

                return plaintext;
            }
        }

        private static async Task<string> DecryptAsync(byte[] encryptedData, ICryptoTransform decryptor)
        {
            using var encryptedStream = new MemoryStream(encryptedData);
            {
                return await DecryptAsync(encryptedStream, decryptor);
            }
        }

        private static async Task DecryptAsync(byte[] encryptedData, ICryptoTransform decryptor, Stream outputStream)
        {
            using (var encryptedStream = new MemoryStream(encryptedData))
            {
                await DecryptAsync(encryptedStream, decryptor, outputStream);
            }
        }

        private static async Task<string> DecryptAsync(Stream encryptedStream, ICryptoTransform decryptor)
        {
            using (var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
            {
                using (var reader = new StreamReader(cryptoStream))
                {
                    return await reader.ReadToEndAsync();
                }
            }
        }

        private static async Task DecryptAsync(
            Stream encryptedStream,
            ICryptoTransform decryptor,
            Stream decryptedStream
        )
        {
            using (var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read))
            {
                await cryptoStream.CopyToAsync(decryptedStream);
            }
        }

        private bool Disposed { get; set; }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (Disposed)
            {
                return;
            }

            if (disposing)
            {
                if (CryptoAlgorithm != null)
                {
                    CryptoAlgorithm.Clear();
                    CryptoAlgorithm.Dispose();
                }
            }

            Disposed = true;
        }
    }
}

//
// Unit Tests
// ---------------------------------------------------------------------------

namespace Tests.Utils
{
    using Xunit;
    using System.Security.Cryptography;
    using Utils;

    public class CryptographerTests
    {
        public static byte[] GenerateSecureRandomKey(int byteSize, bool getNonZeroBytesOnly = false)
        {
            using (var rng = new RNGCryptoServiceProvider())
            {
                var key = new byte[byteSize];

                if (getNonZeroBytesOnly)
                {
                    rng.GetNonZeroBytes(key);
                }
                else
                {
                    rng.GetBytes(key);
                }

                return key;
            }
        }

        [Fact]
        public void EncryptAesManaged()
        {
            const string plainText =
                "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor " +
                "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " +
                "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
                "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " +
                "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit " +
                "anim id est laborum.";

            using (var aes = new AesManaged
            {
                Key = CryptoUtil.GenerateSecureRandomKey(byteSize: 32),
                IV = CryptoUtil.GenerateSecureRandomKey(byteSize: 16),
                Padding = PaddingMode.PKCS7, // also the default
                Mode = CipherMode.CBC, // also the default
                KeySize = 256, // also the default
                BlockSize = 128 // also the default
            })
            {
                using (var encryptor = new Encryptor(aes))
                {
                    var encrypted = encryptor.EncryptAsync(plainText).Result;
                    var decrypted = encryptor.DecryptAsync(encrypted).Result;

                    Assert.Equal(plainText, decrypted);
                }
            }
        }
    }
}