Symmetric Encryption

Cryptography is an increasing requirement for applications, so it is great that it is part of the .NET framework. Matteo builds on his first article that explained Asymmetric Cryptography and Digital Signatures, and tackles Symmetric Encryption and how to implement it in the .NET Framework.

Symmetric encryption is the oldest cryptographic technique used to transmit or store digital data  securely.

Data that has been encrypted with symmetric encryption is considered to be confidential, in the sense that only those entities (persons or systems) who have the key can understand what the data means.

Cryptography is a security-related technology, but it works differently to others. Rather than preventing unauthorized entities from accessing the information, cryptography prevents them from understanding what the data means. Notice that encrypted data is still data, but without meaning in relation to its purpose. So, the encryption process transforms data into other data that has no meaning.

With symmetric encryption, confidentiality is guaranteed by the use of a secret key. With this key, and a mathematical algorithm, data is converted to an unintelligible form. By using the same key, and applying the mathematical algorithm in the reverse way, the original data can be reconstituted. The algorithm is said to be a two-way algorithm.

Symmetric algorithms can be divided into two main classes: stream algorithms and block algorithms. Stream Algorithms operate directly on a stream of bytes whereas block algorithms  operate by transforming fixed-length groups of bits, a block,  at a time Block algorithms are most commonly used in the IT world today.

This article explains which symmetric algorithms are implemented in the .NET framework base classes, and how to apply them to encrypt and decrypt data.

Symmetric Encryption overview

Before I describe how to use symmetric encryption in the .NET framework, I should say a bit about how block symmetric algorithms work.

We have already mentioned that block symmetric algorithms work on blocks of data at a time. The data to be encrypted is divided into blocks of bits of fixed length and a transformation involving the secret key is applied on each block. The type of transformation depends on the selected algorithm.

At this stage, it is important to notice that identical blocks of data are transformed in the same manner inside the blocks sequence. This can be a security issue. Think about an image file. This is composed of blocks of data that, depending on the image format, might set the color of a pixel on the screen. Suppose you want to encrypt this image.  Suppose, also, that you happen to set, as block unit for the image,  the same length of block that specifies a pixel color. By encrypting it, the “pixel-block” will be encrypted into a different block of data. Remember what we say in the introduction. Encrypted data is again data, but with a different meaning. The encryption transforms a block of bytes into a different block of bytes and so a color would, in those circumstances, become a different color.  If the identical blocks are encrypted in the same manner, we obtain an image with colors changed. The result is that the image is still intelligible to the user that sees it.

To solve this issue, algorithms designers introduced different way to use the same symmetric algorithms on the data blocks. Those are referred as Chiper Modes. The most common approach is to take the previous encrypted data block and combine it with the current data block. This introduces some sort of randomness on the output encrypted blocks sequence. The first block is combined with a dummy initial block, named the initialization vector (IV). Sometimes data is not available block by block (as data that comes from a network stream) and the encryption system should retain data in memory until all blocks arrive. This could be dangerous from a security point of view. The issue can be solved by what is referred to as feedback. Small amount of data than those that forms a block are combined with an equal portion of the previous encrypted block until all the block arrives. The final combined block is  then encrypted.

Another issue comes from the block nature of the symmetric algorithm. If the initial data isn’t an exact multiple of the block size utilized, the final block cannot be encrypted. Again, algorithms designers get around this problem by padding the final block with extra data to the required  length. ‘Padding’ refers to the method used to expand the final data block.

The way in which Padding works is not the only influence on the security of the cryptographic algorithm. Another factor is the length of the key used.  The longer the key, the more secure the encrypted data. The trade-off is that  more computational resources are required. For today’s needs, keys of 128-bit or 256-bit length are adopted.

Symmetric Encryption and .NET Framework

The .NET Framework rationalizes the block symmetric algorithms implemented on it by defining the base abstract class SymmetricAlgorithm that you can find under the System.Security.Cryptography namespace. Each symmetric algorithm is derived from it.

The main principal properties defined on SymmetricAlgorithm class are given by:


All the classes that perform symmetric encryption inherit or derive the previous properties. Those classes, contained on the same namespace of the SymmetricAlgorithm class, are given by:


â Aes

            â AesCryptoServiceProvider

            â  AesManaged

â Rjindael

                            â  RjindaelManaged

â  TripleDES

            â  TripleDESCryptoServiceProvider

â  DES

            â  DESCryptoServiceProvider

â  RC2

            â  RC2CryptoServiceProvider

As you can see, algorithms implemented by .NET Framework are: AES, Rjindael, TripleDES, DES and RC2.

Classes on the first level on the derivation tree (the classes Aes ,Rjindael, TripleDES, DES and RC2) define properties related to the specific algorithm. They also implement  the static Create() method that allows us  to create an instance of the object.

The second level on the derivation tree contains all the methods needed by the SymmetricAlgorithm derived object to operate. The two principal methods are given by:


They received, as input, the secret key and the initial IV; and they return a special object that implements the ICryptoTransform interface. We will call this object the Encryptor if we want to encrypt data, otherwise we call it the Decryptor.

This object has methods that permit it to transform arrays of bytes in their encrypted counterpart and vice versa. It is used in conjunction with the CryptoStream object. You can find it under the System.Security.Cryptography namespace.

We will see shortly how to use this object to encrypt/decrypt data with the .NET framework.

Notice that some classes on the second derivation tree end with the suffix CryptoServiceProvider, others with the suffix Managed. The first class of objects utilizes native code to perform cryptographic operation (that relies on the Cryptographic Services Providers defined on the Microsoft® CryptoApi), the second try to utilize only Managed code.

Encrypt and Decrypt Data with .NET Framework

We are now able to use the .NET  Framework base classes to perform encryption and decryption of data.

We will encrypt and decrypt the string “Hello World … !” using, as symmetric algorithm, the Advance Encryption Standard (AES) algorithm.

To do so, we must:

  1. Create an AesCryptoServiceProvider (or an AesManaged) object.
  2. Initialize it with a secret key and an IV (as bytes arrays)
  3. Create the Encryptor or Decryptor object.
  4. Use it in conjunction with a CryptoStream objects that will encrypt and decrypt our data.

We start by creating the secret key and the IV. We need to know their length first. To select the right key and IV lengths, we can browse the LegalKeysSize and LegalBlockSize properties of the SymmetricAlgorithm class. Doing so, we found that 128 bit (and so 16 bytes) is a possible choice for both.  We will take this value.

Notice that, from what we say previously, the IV length must be the same of the block size, being the IV mixed byte by byte with the first block to encrypt.

Now, to create the key, we could select a key phrase, convert it in a byte array with the System.Text.Encoding class and get what we need. This is not the right way to do so. Cryptographic keys must be “well done” in the sense that they must have some “special form” to be secure. Without entering into detail, a good way to create our password is to use the class System.Security.Cryptography.Rfc2898DeriveBytes. This class uses the RFC2898 to generate a “well done” cryptographic key, starting from an initial string. Leaving to the RFC2898 the details about what a ‘salt’ is, ‘salt’, we must generate it first (as a byte array). The length of the ‘salt’ must be at least 8 bytes (that, with ASCII or UTF8 encoding is equal to 8 characters). So:

We can now create the key and the IV:

The GetBytes method of the Rfc2898DeriveBytes is reproducible. Given a fixed salt and password, each time we call the GetBytes(n), with n fixed, we will obtain the same value. This is very important because the key and IV must be recreated each time we want to decrypt data. If the GetBytes method produces different values on every call, we will not be able to decrypt data. Remember to save the ‘salt’ and the initial password in some safe place.

The next step is to encrypt our message. To do so we must create the Encryptor first. We want the Encryptor to encrypt data on the basis of the AES algorithm:

We must now create the CryptoStream object.  Its constructor accepts, as input parameters, a stream, an ICryptoTransform object and a CryptoStreamMode enum value. The stream to pass will be a stream where data will be written in an encrypted form, the ICrpytoTransform object will be the Encryptor created and the CryptoStreamMode enum value will be Write, stating that we want to write encrypted data on the input stream. For this, we can use the MemoryStream class, defined under the System.IO namespace.

Now we are able to encrypt our data. We must transform it into a byte array first:

To encrypt the message we will use the Write method of the CryptoStream object, as we do with all other streams. The encrypted data will be written on the MemoryStream object.

Notice that, as with all other streams defined on the .NET Framework, the Flush method must be performed to complete the writing of data on the underlying stream: The CryptoStream class implements a special FlushFinalBlock() method to do the same thing. The name reminds us the block-nature of the encryption process.

With the FlushFinalBlock method-call, the encryption of our data ends.  At this stage, the encrypted data is stored in the MemoryStream object.  We can read it as we would with all other streams.

To decrypt our data we must perform the same steps as for encryption. In this case, the Decryptor must be created and the CryptoStreamMode must be set to Read. Data to decrypt must be taken from the previous generated stream containing the encrypted data.

Now, our data can be recovered in its original form by simply reading it from the CryptoStream object. We can use the StreamReader  class for this. You can find it under the System.IO namespace:

If you try to check the content of the message variable, you will see that our encrypted message was successfully decrypted and stored on it.


We end this article by providing some links so you can go deeper into the concepts we’ve touched on: It describes the most used Chiper Modes for symmetric encryption. In it, you will find an interesting example about images encryption in relation to the Chiper Mode used. It contains information about the Padding Modes more often utilized. Link to the RFC 2898 that describe the way in which “well done” keys can be derived from an initial password. Official AES (Advance Encryption Standard) specifications. In it you can discover how some concepts explained in this papers are keep in consideration when a new standard is made.