Overview of symmetric encryption in .NET

Introduction

A symmetric encryption algorithm is one where the cryptographic key is the same for both encryption and decryption and is shared among the parties involved in the process.

Ideally only a small group of reliable people should have access to this key. Attackers decipher an encrypted message rather than trying to defeat the algorithm itself. The key can vary in size so the attacker will need to know this first. Once they know this then they will try combinations of possible key characters.

A clear disadvantage with this approach is that distributing and storing keys in a safe and reliable manner is difficult. On the other hand symmetric algorithms are fast.

In this short overview we’ll look at the symmetric encryption algorithms currently supported in .NET.

Symmetric encryption in general

We start with the plain text to be encrypted, such as “this is a secret message”. The encryption algorithm runs using the common secret key. The plain text becomes ciphertext which is decrypted using the same secret key and algorithm.

There are 3 symmetric algorithms supported in .NET which all derive from the SymmetricAlgorithm base class in the System.Security.Cryptography namespace:

  • Data encryption standard (DES), today considered obsolete for modern applications but is still prevalent in legacy software
  • Triple DES: applies DES encryption 3 times with either 2 or 3 different cryptographic keys. In the 2-key scenario the same key is applied in the first and the third encryption iteration
  • Advanced Encryption Standard (AES) with Rijndael: the current modern standard which is considered secure and close to impossible to break by brute force

The size of the encryption key varies depending on the algorithm. Generally the larger the key the more difficult it is to find the correct combination of bytes for an attacker.

There are other symmetric algorithms out there, such as Mars, RC6, Serpent, TwoFish, but there’s no .NET implementation of them at the time of writing this post. Make sure to pick AES/Rijndael as your first choice if you need to select a symmetric algorithm in your project.

Other terms

There are other important terms related to symmetric encryption that you’ll come across:

  • Padding: it happens that the encrypted cipher is shorter than the number of bytes produced by the algorithm. In this case the missing bytes must be filled in some way. The available options are ANSI X923, ISO 10126, None, PKCS7, Zeros. The default is PKCS7 where each missing byte is filled with the number that shows the number of padded bytes. Normally there’s no need to change the default padding mode. The padding can be changed through a public property of SymmetricAlgorithm where you’ll find these padding modes as an enumeration
  • Cipher mode or encryption mode: the cipher mode declares how the various blocks in the byte array to be encrypted. The default mode is called cipher block chaining (CBC) where the encryption of each block depends on the feedback from encrypting the previous block. This ensures that identical sections in the bytes to be encrypted will look different. You can set the exact cipher mode by way of a public property just like in the case of the padding mode above. Normally you shouldn’t change the default cipher mode
  • Initialization vector (IV), also called a nonce: it is a byte array that adds even more “salt” to the encryption process. The IV determines what kind of random data is applied to use for the first encryption block simply because there’s no block before the first block to be used as input. The IV is just some random byte array that will be used as input in the encryption of the first block. The IV doesn’t need to be secret. It must be redistributed along with the cipher text to the receiver of our message. The only rule is not to reuse the IV to keep the randomness that comes with it.
  • Block size: the size of the various sections or blocks in the full byte array to be encrypted

The various SymmetricAlgorithm implementations can all generate these values for us and we’ll take advantage of this in the code demo. Also, we won’t change the padding and cipher modes, we’ll stick to the recommended defaults.

Code example

We’ll keep the result of the encryption process in a container object:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SymmetricEnryption
{
	public class SymmetricEncryptionResult
	{
		public byte[] Cipher { get; set; }
		public string CipherBase64 { get; set; }
		public byte[] IV { get; set; }
		public byte[] SymmetricKey { get; set; }
		public bool Success { get; set; }
		public string ExceptionMessage { get; set; }
	}
}

Here’s the SymmetricEncryptionService with both the encrypt and decrypt functions:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;

namespace SymmetricEnryption
{
    public class SymmetricEncryptionService
    {
		public SymmetricEncryptionResult Encrypt(string messageToEncrypt, int symmetricKeyLengthBits, 
			SymmetricAlgorithm algorithm)
		{
			SymmetricEncryptionResult encryptionResult = new SymmetricEncryptionResult();
			try
			{
				//first test if bit length is valid
				if (algorithm.ValidKeySize(symmetricKeyLengthBits))
				{
					algorithm.KeySize = symmetricKeyLengthBits;
					using (MemoryStream mem = new MemoryStream())
					{
						CryptoStream crypto = new CryptoStream(mem, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
						byte[] bytesToEncrypt = Encoding.UTF8.GetBytes(messageToEncrypt);
						crypto.Write(bytesToEncrypt, 0, bytesToEncrypt.Length);
						crypto.FlushFinalBlock();
						byte[] encryptedBytes = mem.ToArray();
						string encryptedBytesBase64 = Convert.ToBase64String(encryptedBytes);
						encryptionResult.Success = true;
						encryptionResult.Cipher = encryptedBytes;
						encryptionResult.CipherBase64 = encryptedBytesBase64;
						encryptionResult.IV = algorithm.IV;
						encryptionResult.SymmetricKey = algorithm.Key;
					}
				}
				else
				{
					string NL = Environment.NewLine;
					StringBuilder exceptionMessageBuilder = new StringBuilder();
					exceptionMessageBuilder.Append("The provided key size - ")
						.Append(symmetricKeyLengthBits).Append(" bits - is not valid for this algorithm.");
					exceptionMessageBuilder.Append(NL)
						.Append("Valid key sizes: ").Append(NL);
					KeySizes[] validKeySizes = algorithm.LegalKeySizes;
					foreach (KeySizes keySizes in validKeySizes)
					{
						exceptionMessageBuilder.Append("Min: ")
							.Append(keySizes.MinSize).Append(NL)
							.Append("Max: ").Append(keySizes.MaxSize).Append(NL)
							.Append("Step: ").Append(keySizes.SkipSize);
					}
					throw new CryptographicException(exceptionMessageBuilder.ToString());
				}
			}
			catch (Exception ex)
			{
				encryptionResult.Success = false;
				encryptionResult.ExceptionMessage = ex.Message;
			}

			return encryptionResult;
		}

		public string Decrypt(byte[] cipherTextBytes, byte[] key, byte[] iv, SymmetricAlgorithm algorithm)
		{
			algorithm.IV = iv;
			algorithm.Key = key;
			using (MemoryStream mem = new MemoryStream())
			{
				CryptoStream crypto = new CryptoStream(mem, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
				crypto.Write(cipherTextBytes, 0, cipherTextBytes.Length);
				crypto.FlushFinalBlock();
				return Encoding.UTF8.GetString(mem.ToArray());
			}
		}
    }
}

The Encrypt function first checks if the provided key length in bits is valid for the concrete symmetric algorithm. If not then we build a message with the available key sizes using the LegalKeySizes property. The CryptoStream class is a special stream for cryptographic operations. Note how the CreateEncryptor() function is applied in the constructor. We use CreateDecryptor() in the Decrypt function instead. We let the crypto stream write the bytes to the memory stream and read the various components such as the IV and the key at the end.

The Decrypt function is virtually identical except for the CreateDecryptor() bit.

Usage

We’ll try all 3 encryption algorithms in the demo code. Actually, the AES standard is implemented in 2 ways: AesCryptoServiceProvider and AesManaged. AesManaged is a .NET specific implementation of AES whereas AesCryptoServiceProvider is based on the cryptographic libraries of Windows. This latter library has also been officially certified by the National Institute of Standards and Technology. Normally the two libraries produce the same output. If you want to make sure that a 100% valid and certified implementation of AES is used in your code then use AesCryptoServiceProvider.

Here’s the test code:

private void TestSymmetricEncryptDecrypt()
{
	string originalMessage = "This is an extremely secret message.";
	List<SymmetricAlgorithm> symmAlgos = new List<SymmetricAlgorithm>()
	{
		new DESCryptoServiceProvider() { KeySize = 64 },
		new TripleDESCryptoServiceProvider() {KeySize = 128 },
		new AesCryptoServiceProvider() { KeySize = 128 },
		new AesManaged() { KeySize = 128 }
	};
	foreach (SymmetricAlgorithm symmAlg in symmAlgos)
	{
		SymmetricEncryptionService symmService = new SymmetricEncryptionService();
		SymmetricEncryptionResult encryptionResult = symmService.Encrypt(originalMessage, symmAlg.KeySize, symmAlg);
		Console.WriteLine(string.Concat("Encryption result with ", symmAlg.GetType().Name));
		Console.WriteLine("==============================================");
		Console.WriteLine(string.Concat("Success: ", encryptionResult.Success));
		if (encryptionResult.Success)
		{
			Console.WriteLine(string.Concat("Original message: ", originalMessage));
			Console.WriteLine(string.Concat("Symmetric key: ", Convert.ToBase64String(encryptionResult.SymmetricKey)));
			Console.WriteLine(string.Concat("Initialisation vector: ", Convert.ToBase64String(encryptionResult.IV)));
			Console.WriteLine(string.Concat("Ciphertext: ", encryptionResult.CipherBase64));
			Console.WriteLine();
			string decrypted = symmService.Decrypt(encryptionResult.Cipher, encryptionResult.SymmetricKey, encryptionResult.IV, symmAlg);
			Console.WriteLine(string.Concat("Decrypted message: ", decrypted));
		}
		else
		{
			Console.WriteLine(string.Concat("Exception message: ", encryptionResult.ExceptionMessage));
		}
		Console.WriteLine("==============================================");
	}
}

Here’s the output:

Encryption result with DESCryptoServiceProvider
==============================================
Success: True
Original message: This is an extremely secret message.
Symmetric key: V9eC6eooMRk=
Initialisation vector: cMaYr/rAv9U=
Ciphertext: yO+PAsYvmmocDpNXxxSpCz2taGF720BBCVGUlP2Pk6rOPCMBkCXLYQ==

Decrypted message: This is an extremely secret message.
==============================================
Encryption result with TripleDESCryptoServiceProvider
==============================================
Success: True
Original message: This is an extremely secret message.
Symmetric key: hcljhykC9nsk1kwa7EztEg==
Initialisation vector: 4n8qx5M/Gf4=
Ciphertext: rXeL3QXiJZb+Sk5ibXsfCpDRto1esBeIPpFi9AAtcB4ZiqQmfwdcJg==

Decrypted message: This is an extremely secret message.
==============================================
Encryption result with AesCryptoServiceProvider
==============================================
Success: True
Original message: This is an extremely secret message.
Symmetric key: z2VxpjGTXpSiXIhgDaosUA==
Initialisation vector: dDX3HvnDU/H6RWwk/I+8Ag==
Ciphertext: roA4geDmVN7HV8F66hy6CtPlygtEMhlnWEWpo+Q7f1UDA1qMvHcVw00/pJmLOEvw

Decrypted message: This is an extremely secret message.
==============================================
Encryption result with AesManaged
==============================================
Success: True
Original message: This is an extremely secret message.
Symmetric key: zhrGzXuIowQknliwbz45tA==
Initialisation vector: Xn9X+EGCCfYF6UQeJLrxTw==
Ciphertext: sedii0OQLJx8XKjvbo1y/geq2adJwma4o/IihPr29asdlAXenatVRlLPZEtSA8J+

Decrypted message: This is an extremely secret message.
==============================================

If we deliberately provide an invalid key size, like here…:

SymmetricEncryptionResult encryptionResult = symmService.Encrypt(originalMessage, 10, symmAlg);

…then we get the valid key sizes instead:

Encryption result with DESCryptoServiceProvider
==============================================
Success: False
Exception message: The provided key size – 10 bits – is not valid for this algorithm.
Valid key sizes:
Min: 64
Max: 64
Step: 0
==============================================
Encryption result with TripleDESCryptoServiceProvider
==============================================
Success: False
Exception message: The provided key size – 10 bits – is not valid for this algorithm.
Valid key sizes:
Min: 128
Max: 192
Step: 64
==============================================
Encryption result with AesCryptoServiceProvider
==============================================
Success: False
Exception message: The provided key size – 10 bits – is not valid for this algorithm.
Valid key sizes:
Min: 128
Max: 256
Step: 64
==============================================
Encryption result with AesManaged
==============================================
Success: False
Exception message: The provided key size – 10 bits – is not valid for this algorithm.
Valid key sizes:
Min: 128
Max: 256
Step: 64
==============================================

The step property means the possible increment size between min and max. E.g. for AES the minimum key size is 128 bits with 128 + 64 = 192 bits as the next valid key size.

You can view the list of posts on Security and Cryptography here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

3 Responses to Overview of symmetric encryption in .NET

  1. Suresh.G says:

    Which symmetric encryption methods / technique are supported by .net 5.0

    • Andras Nemes says:

      I have no information on what’s coming in .NET 5.0 so I unfortunately cannot help you there. //Andras

      • Suresh.G says:

        I would like to Encrypt / Decrypt an password string using .Net core in client / server environment what the best options in terms of Performance and Security.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.