Encryption + Decryption extension methods

Symmetric encryption & decryption methods below created as extension methods over the abstract SymmetricAlgorithm class.  This is to allow any cryptographic algorithm the capability to directly encrypt and decrypt string data (not have to work directly with the bytes and care about the possible encodings).

public static class SymmetricAlgorithmExtensions
{
	public static byte[] Encrypt(this SymmetricAlgorithm crypto, string unencryptedText)
	{
		using (var memStream = new MemoryStream())
		using (var csEncrypt = new CryptoStream(memStream, crypto.CreateEncryptor(crypto.Key, crypto.IV), CryptoStreamMode.Write))
		using (var writer = new StreamWriter(csEncrypt))
		{
			foreach (var textChar in unencryptedText.ToCharArray())
			{
				writer.Write(textChar);
			}
			writer.Flush();
			// If you do not close the writer before returning the memStream you will get an invalid padding when decrypting
			writer.Close();
			return memStream.ToArray();
		}
	}

	public static string Decrypt(this SymmetricAlgorithm crypto, byte[] encryptedBytes)
	{
		using (var inputStream = new MemoryStream(encryptedBytes))
		using (var csDecrypt = new CryptoStream(inputStream, crypto.CreateDecryptor(crypto.Key, crypto.IV), CryptoStreamMode.Read))
		using (var reader = new StreamReader(csDecrypt))
		{
			return reader.ReadToEnd();
		}
	}
}

Test Method

[TestMethod]    
public void EncryptDecryptStringTest()
{
	const string message = "Mittens on kittens";
	var crypto = CryptoHelper.Default();
	var encrypted = crypto.Encrypt(message);
	var decrypted = crypto.Decrypt(encrypted);
	Assert.AreEqual(message, decrypted);
}

Creating an AESManaged 256 bit cryptography

  • With SetInput lengths for key and initialization vector
  • It is important that you use the Rfc2898DeriveBytes function to derive your bytes in a deterministic manner with AES otherwise the key + salt will not be the same if used multiple times by the cryptography i.e. if you create the crypto algorithm encrypt something, dispose of the algorithm and then recreate it again to decrypt in a separate method or process the keys + salt bytes will be different and not work properly.
public static class CryptoHelper
{
	private static string SetInput(this string input, int length, char padChar = '0')
	{
		input = input ?? string.Empty;
		if (input.Length < length)
		{
			input = input.PadRight(length, padChar);
		}
		return string.Join("", input.Take(length));
	}

	public static SymmetricAlgorithm GetAESManaged(string password, string initVector = "")
	{
		if (string.IsNullOrWhiteSpace(password) && string.IsNullOrWhiteSpace(initVector))
		{
			return null;
		}
		password = password.SetInput(KeySize);
		initVector = (string.IsNullOrEmpty(initVector) ? password : initVector).SetInput(InitVectorSize);
		using (var rfc2898 = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(initVector)))
		{
			var crypto = new AesManaged
			{
				// https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.ciphermode?view=netframework-4.8
				Mode = CipherMode.CBC, // This is important for decrypting on an appended block file
				Padding = PaddingMode.PKCS7,
				KeySize = KeySize * BitsInByte
			};
			crypto.Key = rfc2898.GetBytes(crypto.KeySize / BitsInByte);
			crypto.IV = rfc2898.GetBytes(crypto.BlockSize / BitsInByte);
			return crypto;
		}
	}

}

Special class if you want to do text lines or files encryption/decryption line by line

Uses the above SymmetricAlgorithm encrypt/decrypt functions.  Added a parallelism throttle at 75% of the current machines CPU count to avoid buffer overflow when working with large sets of lines.

public static class SymmetricAlgorithmLineExtensions
{
	public const int KeySize = 32;
	public const int InitVectorSize = 16;
	public const int BitsInByte = 8;
	public static readonly string DefaultLineDelimiter = Environment.NewLine;
	private static Func<int> _maxParallelismFn;

	public static Func<int> MaxParallelismFn
	{
		get => _maxParallelismFn ?? (() => (int) Math.Ceiling(Environment.ProcessorCount * .75));
		set => _maxParallelismFn = value;
	}

	public static Span<string> EncryptInParallel(this SymmetricAlgorithm crypto, IEnumerable<string> lines)
	{
		var encryptedLines = new ConcurrentDictionary<long, string>();
		var parallelism = MaxParallelismFn.Invoke();
		parallelism = parallelism <= 0 ? Environment.ProcessorCount : parallelism;

		var enumerable = (lines ?? new List<string>()).ToArray();
		Parallel.ForEach(enumerable,
			new ParallelOptions {MaxDegreeOfParallelism = parallelism },
			(encryptedLine, _, lineNumber) =>
			{
				var encrypted = crypto.EncryptLine(encryptedLine);
				encryptedLines[lineNumber] = encrypted;
			});
		return encryptedLines.Values.ToArray().AsSpan();
	}

	public static Span<string> DecryptInParallel(this SymmetricAlgorithm crypto, IEnumerable<string> lines)
	{
		var decryptedLines = new ConcurrentDictionary<long, string>();
		var parallelism = MaxParallelismFn.Invoke();
		parallelism = parallelism <= 0 ? Environment.ProcessorCount : parallelism;

		var enumerable = (lines ?? new List<string>()).ToArray();
		Parallel.ForEach(enumerable,
			new ParallelOptions {MaxDegreeOfParallelism = parallelism },
			(encryptedLine, _, lineNumber) =>
			{
				var decrypted = crypto.DecryptLine(encryptedLine);
				decryptedLines[lineNumber] = decrypted;
			});
		return decryptedLines.Values.ToArray().AsSpan();
	}
}