Click here to Skip to main content
Click here to Skip to main content

Swanky Encryption/Decryption in C#

, 30 Sep 2014 MIT
Rate this:
Please Sign up or sign in to vote.
Handy classes to use the .NET Encryption/Decryption

Introduction

Using the built in crypto classes in .NET can be surprisingly complicated. You need a lot of understanding of what you are doing to get it right and how to work in a secure manner. The choice of algorithm, what cipher mode, key length, block size and understand what salt is and how to use it and also how to hash a password to a proper key are some things you need to deal with. Hopefully, this article will make your life a lot easier.

My implementation is a class library with useful functions which will help you with the most common scenarios. I have also added something I call AutoSalt which is a way to secure your data further with no extra effort from you. Furthermore, I have done my own implementation of what CryptoStream is doing to get some extra control on what is going on in the stream.

Background

The reason I started this article is because I started looking into encryption with CryptoStream and I was not able to achieve what I needed in a project. The reason is that the CryptoStream is limited to push everything through a stream and there is no way you can pick up the data while it's being encrypted. I guess this part is a bit specialized for my needs but I don't have a doubt that there are more people out there with similar issues.

A simple example of what you can't do with CryptoStream that I can easily handle with my implementation is to split a 50GB file into a series of smaller files with 100MB data each in one go. You may have seen .rar files split up like this, it's kind of the same idea.

This part of my project then evolved to writing a full library and then I just felt like sharing with this article.

Why Use this Lib

For me, security has been a real concern when coming up with this library. Because encryption is not just down to the algorithm, it's also about how you store keys generate keys/Salt in a secure manner, making sure that your application has a safe flow in how the data is handled, and also making sure you made the right choices of how to set up your encryption in a good way. Even performance could play an important part if you are dealing with huge sets of data.

Obviously, I am not solving all of these problems but some of them, but I will make it easier for you so that you can focus on quality in your code.

  • I have added a feature called AutoSalt which creates cryptographically safe random data and combines this with the data. So if you encrypt the same thing twice, the encrypted data will look different.
  • I have made sure you can utilize hardware acceleration if supported by your hardware.
  • I generate key and Initvector in a good practice.
  • You have the choice to use most setups available in .NET
  • For simple scenarios, the provided helper class will give you one liners to use the encryption in a simple way.
  • For advanced users, you will be able to cut in and read from the encrypted/decrypted data stream before the entire process is complete.

So anyone that just wants to encrypt something or has issues using CryptoStream because it does not do what you want it to do should use this library instead.

Using the Code

This library is built for being simple to use but with flexibility and security in mind. Therefore, you have access to the main classes to be able to do more advanced things and the helper classes that will help you in doing oneliners.

You will be able to choose one of the following symmetric algorithms, key size and cipher mode and block size when you work with encryption. I default to AES with 256bit key and CBC cipher mode because it's the current standard and if your CPU is not too old, CPU hardware acceleration will be available which is always nice.

The combinations from the above grid are expressed as options in the following Enum. It is formatted like this: [Symmetric Algorithm]_[Key Size]_[Cipher Mode]_[Block Size]. I choose to create an Enum like this because there are so few combinations shared by the algorithms and it's also nice to never allow you to give an incorrect option. The block size is only available for Rijndael because it is the only symmetric algorithm that supports it.

public enum SymmetricCryptoAlgorithm
{
    AES_128_CBC,
    AES_128_ECB,
    AES_192_CBC,
    AES_192_ECB,
    AES_256_CBC,
    AES_256_ECB,

    Rijndael_128_CBC_128,
    Rijndael_128_CBC_192,
    Rijndael_128_CBC_256,
    Rijndael_128_ECB_128,
    Rijndael_128_ECB_192,
    Rijndael_128_ECB_256,
    Rijndael_128_CFB_128,
    Rijndael_128_CFB_192,
    Rijndael_128_CFB_256,
    Rijndael_192_CBC_128,
    Rijndael_192_CBC_192,
    Rijndael_192_CBC_256,
    Rijndael_192_ECB_128,
    Rijndael_192_ECB_192,
    Rijndael_192_ECB_256,
    Rijndael_192_CFB_128,
    Rijndael_192_CFB_192,
    Rijndael_192_CFB_256,
    Rijndael_256_CBC_128,
    Rijndael_256_CBC_192,
    Rijndael_256_CBC_256,
    Rijndael_256_ECB_128,
    Rijndael_256_ECB_192,
    Rijndael_256_ECB_256,
    Rijndael_256_CFB_128,
    Rijndael_256_CFB_192,
    Rijndael_256_CFB_256,

    DES_64_CBC,
    DES_64_ECB,
    DES_64_CFB,

    TripleDES_128_CBC,
    TripleDES_128_ECB,
    TripleDES_128_CFB,
    TripleDES_192_CBC,
    TripleDES_192_ECB,
    TripleDES_192_CFB,

    RC2_40_CBC,
    RC2_40_ECB,
    RC2_40_CFB,
    RC2_48_CBC,
    RC2_48_ECB,
    RC2_48_CFB,
    RC2_56_CBC,
    RC2_56_ECB,
    RC2_56_CFB,
    RC2_64_CBC,
    RC2_64_ECB,
    RC2_64_CFB,
    RC2_72_CBC,
    RC2_72_ECB,
    RC2_72_CFB,
    RC2_80_CBC,
    RC2_80_ECB,
    RC2_80_CFB,
    RC2_88_CBC,
    RC2_88_ECB,
    RC2_88_CFB,
    RC2_96_CBC,
    RC2_96_ECB,
    RC2_96_CFB,
    RC2_104_CBC,
    RC2_104_ECB,
    RC2_104_CFB,
    RC2_112_CBC,
    RC2_112_ECB,
    RC2_112_CFB,
    RC2_120_CBC,
    RC2_120_ECB,
    RC2_120_CFB,
    RC2_128_CBC,
    RC2_128_ECB,
    RC2_128_CFB
}

In the example code I have provided, you will be able to see a comparison of performance between all different setups which I think will help you decide what to use depending on what you use it for. I also try to illustrate the different ways of working.

This is an example using my helper class which will help you do one liners for encrypting or decrypting data.

string key = "My super secret password";
string salt = "Salt value";
string dataString = "My secret data";
byte[] dataBytes = Encoding.Unicode.GetBytes(dataString);
            
// Encrypt/Decrypt strings
var encStr = CryptoHelper.EncryptString
(password, salt, dataString, SymmetricCryptoAlgorithm.TripleDES_192_CBC);
var decStr = CryptoHelper.DecryptString
(password, salt, encStr, SymmetricCryptoAlgorithm.TripleDES_192_CBC);

// Encrypt/Decrypt strings with AutoSalt
var encryptedStringAutoSalt = 
CryptoHelper.EncryptString(password, AutoSaltSizes.Salt32, dataString);
var decryptedStringAutoSalt = 
CryptoHelper.DecryptString(password, AutoSaltSizes.Salt32, encryptedStringAutoSalt);

// Encrypt/Decrypt byte arrays
var encryptedBytes = CryptoHelper.EncryptData(password, salt, dataBytes);
var decryptedBytes = CryptoHelper.DecryptData(password, salt, encryptedBytes);

// Encrypt/Decrypt byte arrays with AutoSalt
var encryptedBytesAutoSalt = CryptoHelper.EncryptData
(password, AutoSaltSizes.Salt64, dataBytes);
var decryptedBytesAutoSalt = CryptoHelper.DecryptData
(password, AutoSaltSizes.Salt64, encryptedBytesAutoSalt);

// Encrypt/Decrypt files
CryptoHelper.EncryptFile(password, salt, "testdata.txt", "testdata encrypted.txt");
CryptoHelper.DecryptFile(password, salt, "testdata encrypted.txt", "testdata decrypted.txt");

// Encrypt/Decrypt files with AutoSalt
CryptoHelper.EncryptFile(password, AutoSaltSizes.Salt128, 
"testdata.txt", "testdata encrypted.txt");
CryptoHelper.DecryptFile(password, AutoSaltSizes.Salt128, 
"testdata encrypted.txt", "testdata decrypted.txt");

The following example shows how to use the main classes in the simplest scenario.

Important detail: Because of the nature of how the encryption classes work, it wants to know when I'm done entering data into the buffer, it needs to know this so that it can add padding to the data. To handle this in my code, you need to set the lastData flag to true when all data is added in the AddData function.

string dataStr = "My super secret data that I want to encrypt";
byte[] bytes = Encoding.Unicode.GetBytes(dataStr);

EncryptionBuffer encBuffer = new EncryptionBuffer("My super secret password", 
"Salt value", SymmetricCryptoAlgorithm.AES_256_CBC);
encBuffer.AddData(bytes, true);
byte[] encryptedBytes = encBuffer.GetData();
string encryptedString = Encoding.Unicode.GetString(encryptedBytes);

DecryptionBuffer decBuffer = new DecryptionBuffer("My super secret password", 
"Salt value", SymmetricCryptoAlgorithm.AES_256_CBC);
decBuffer.AddData(encryptedBytes, true);
byte[] decryptedBytes = decBuffer.GetData();
string decryptedString = Encoding.Unicode.GetString(decryptedBytes);

Here is a bit more advanced example where I read a file in chunks and encrypt the chunks and then save it down to another file, and then back again. This example can be easily modified to do whatever you like it to do. It should be thread-safe so that you can read and write data at the same time from two different threads if that is needed.

// Encrypting a file and writing the encrypted file at the same time
using (FileStream originalFile = new FileStream("testdata.txt", FileMode.Open, FileAccess.Read))
{
    using (FileStream encryptedFile = 
    new FileStream("testdata.enc", FileMode.OpenOrCreate, FileAccess.Write))
    {
        EncryptionBuffer encBuffer = 
        new EncryptionBuffer("My super secret password", "Salt value");
        byte[] fileData = new byte[10000];
        bool isLastData = false;
        while (!isLastData)
        {
            int nrOfBytes = originalFile.Read(fileData, 0, fileData.Length);
            isLastData = (nrOfBytes == 0);

            encBuffer.AddData(fileData, 0, nrOfBytes, isLastData);
            byte[] encryptedData = encBuffer.GetData();
            encryptedFile.Write(encryptedData, 0, encryptedData.Length);
        }
    }
}

// Decrypting the encrypted file
using (FileStream encryptedFile = 
new FileStream("testdata.enc", FileMode.Open, FileAccess.Read))
{
    using (FileStream decryptedFile = 
    new FileStream("testdata.dec", FileMode.OpenOrCreate, FileAccess.Write))
    {
        DecryptionBuffer decBuffer = 
        new DecryptionBuffer("My super secret password", "Salt value");
        byte[] fileData = new byte[10000];
        bool isLastData = false;
        while (!isLastData)
        {
            int nrOfBytes = encryptedFile.Read(fileData, 0, fileData.Length);
            isLastData = (nrOfBytes == 0);

            decBuffer.AddData(fileData, 0, nrOfBytes, isLastData);
            byte[] decryptedData = decBuffer.GetData();
            decryptedFile.Write(decryptedData, 0, decryptedData.Length);
        }
    }
}

AutoSalt and Why Use It

Using Salt is most commonly associated with storing password hashes and not so much with encrypting data. But obviously, it can be used for storing encrypted data as well. Salt is actually supposed to be a public key made out of 100% random data. It should also be stored together with the encrypted data.

The reason for using salt is to further obfuscate the data, mostly to hide patterns. If you just encrypt the same data multiple times, the encrypted result will look exactly the same. Introducing a proper salt to this data will make the result look different every time.

What I am basically doing here with my AutoSalt is generating a random salt value with a length you can set, the salt key will then be concatenated to the encrypted data. So if you choose to encrypt your data with a AutoSaltSizes.Salt256 then 32 bytes of random data will be added to your encrypted data.

public enum AutoSaltSizes:int
{
    Salt32 = 4,
    Salt64 = 8,
    Salt128 = 16,
    Salt192 = 24,
    Salt256 = 32,
    Salt384 = 48,
    Salt512 = 64
}

Here is an example of how to use the code with AutoSalt. It's so easy to use and together with the added security value, you should probably never consider not using it. Dealing with saltvalues yourself will only give you more work.

string dataString = "My secret data";
string enc = CryptoHelper.EncryptString("password", AutoSaltSizes.Salt64, dataString);
string dec = CryptoHelper.DecryptString("password", AutoSaltSizes.Salt64, enc);

Here is an extract from my example code demonstrating the result of encrypting the same data with different outputs.

Performance

Here is a comparison between the different crypto algorithms encrypting/decrypting exactly the same data. The way you read the labels in the x axis is ALGORITHM_KEYSIZE_CIPHERMODE_BLOCKSIZE (blocksize is only there for Rijndael because it's the only one that can change it).

You can clearly see that AES is way faster than anything else. The reason for this is that I use AesCryptoServiceProvider rather than AesManaged. AesCryptoServiceProvider uses the Windows OS API which has support for AES-NI whenever it's available, i.e., hardware accelerated encryption supported by the newer intel CPU:s (like the Core and Xeon series). AesManaged is a completely managed implementation of the AES algorithm and will never make use of hardware acceleration.

In this lib, I just ignored the Managed one since they provide the same result but slower. For the other algorithms, there is also CryptoServiceProviders except Rijndael which only exists as Managed. So if you have older hardware, you might have hardware accelerated support even for this.

You can also see that the ciphermode CFB seems to slow down the algorithms quite a lot except for smaller blocksize Rijndael. I think CFB should be avoided unless possibly you think a slow algorithm adds to your security.

If you want to write something with high performance and good security, you should probably always use AES_256_CBC. Rijndael is said to be the most secure Algorithm because AES is just a subset of Rinjdael and it supports higher blocksizes and CFB cipher mode, but I can't justify the use of it because of the lack of hardware support.

Click here to see the full picture encryption-decryption...

Points of Interest

What is salt and why/how is it normally used?

I have used salt in encryption which I call AutoSalt in my library, salt is however more commonly used when storing passwords for user accounts.

If you ever have built a web application, you probably have come across that you have to handle useraccounts somehow. As you already hopefully then realized, storing passwords for useraccounts in clear text or in a reversable encrypted manner is not a good idea. The way to go is to make a hashvalue of the passwords that cannot be reversed. This is however not really so secure either unless you use the proper hash algorithm to do it. Also, to further strengthen the hash, you should use a salt.

A salt is nothing more than a random set of data that is hashed in to the password. The salt value should be unique for every set of password that you hash and does not necessarily need to be secret, it can be stored together with the hashed value in a database table for example.

The benefit of using a salt like this is that the salted hash value that you store will be unique for every value you hash. So even if I store the same password for two different users, the hash will be different because of the salt. This will prevent an attacker that has stolen your database from using precompiling lookups, reverse lookups or rainbow tables that he could have prepared because he wouldn't know the hash beforehand. The attacker needs to do his attack on every single record which will be much harder and much more time consuming.

A common example of password hashing the wrong way would be to use an MD5 hash on a password without a salt. there are sites like http://www.md5online.org/ that can act as a reverse lookup where you put the hashed value and you get what the original data was. With a salt, this would not even have been so easy to reverse the password even though MD5 is not considered safe enough.

There is a lot more to say about password hashing which I feel is a bit off topic from my article. If you want to learn more about the proper way of salting, try reading more here.

I implement salts using Rfc2898DeriveBytes. This is the recommended class to use and enables me to easily combine a password and salt value and give me bytes for both key and init vector. The salt value itself is generated with RNGCryptoServiceProvider which is the recommended class to use for generating cryptographically safe data.

With the following code, you can generate a salt and a hash value that is perfectly safe to store for user account purposes.

byte[] salt = new byte[128];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    rng.GetBytes(salt);
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes("user password", salt, 100);
string passwordhash = Convert.ToBase64String(key.GetBytes(100));

What is a symmetric algorithm and which one do we use?

A symmetric algorithm (a.k.a. secret key algorithm) is an algorithm which uses the same key to encrypt or decrypt the data. The ones supported in .NET framework are the following:

  • DES
  • RC2
  • TripleDES
  • AES
  • Rijndael

In my implementation, you can choose either of them. The standard today is AES and that is the reason I default to it in my library.

AES is a subset of Rijndael which is said to be the more secure. I suppose the reason is that Rijndael supports larger blocksizes and CFB cipher mode. I am not sure how much this improves security though. Rinjdael does not have any support for hardware acceleration in .NET which AES has, so I would say that you should probably always go for AES.

Cipher Mode

.NET supports many different cipher modes. Any of them will do as long as you avoid ECB. I think the following wikipage explains this pretty well.

Padding and padding mode

One common question/confusion is why the encrypted data is always longer than the original data. The reason is the BlockSize parameter you can set on the SymmetricAlgorithm. Depending on the Algorithm you choose, you get different blocksizes.

For example, if we have a Blocksize of 128bits (16 bytes), it means size of an encrypted data will always be equally dividable by 16 bytes, also there will always be a padding, so if the size of what you encrypt is already dividable by 16 you will get an extra block of padding. The padding will never be longer than the blocksize.

Example of 128 bit blocksize:

  • If you encrypt 27 bytes, you will end up with 32 bytes of encrypted data.
  • If you encrypt 32 bytes, you will end up with 48 bytes of encrypted data.

Example of 64 bit blocksize:

  • If you encrypt 27 bytes, you will end up with 32 bytes of encrypted data.
  • If you encrypt 32 bytes, you will end up with 40 bytes of encrypted data.

When you decrypt the data, this padding bytes will of course disappear.

In my implementation, I don’t give any option to change the blocksize or padding and I don’t see much reason why you would change the padding type unless you are trying to make the data compatible with another programming language maybe. I use the default Padding which is PKCS7 and the default blocksize which depends on the algorithm.

If you are interested in digging into the padding more in detail, you can read more about it at http://en.wikipedia.org/wiki/Padding_(cryptography) and you can read about block size at http://en.wikipedia.org/wiki/Block_size_(cryptography).

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

1337Architect
Architect
Switzerland Switzerland
No Biography provided

Comments and Discussions

 
QuestionEncrypt and Decrypt String problem [modified] Pinmemberklaydze23-Nov-14 22:55 
AnswerRe: Encrypt and Decrypt String problem Pinmember1337Architect23-Nov-14 23:15 
GeneralRe: Encrypt and Decrypt String problem [modified] Pinmemberklaydze23-Nov-14 23:29 
QuestionDecryption using encryptedString PinmemberFrancisco Manuel Suárez Grueso29-Sep-14 6:55 
AnswerRe: Decryption using encryptedString Pinmember1337Architect29-Sep-14 10:27 
GeneralRe: Decryption using encryptedString PinmemberFrancisco Manuel Suárez Grueso29-Sep-14 21:14 
GeneralRe: Decryption using encryptedString Pinmember1337Architect29-Sep-14 22:45 
GeneralRe: Decryption using encryptedString PinmemberFrancisco Manuel Suárez Grueso29-Sep-14 23:01 
GeneralRe: Decryption using encryptedString Pinmember1337Architect29-Sep-14 23:10 
GeneralRe: Decryption using encryptedString PinmemberFrancisco Manuel Suárez Grueso30-Sep-14 2:14 
GeneralRe: Decryption using encryptedString Pinmember1337Architect30-Sep-14 1:36 
GeneralRe: Decryption using encryptedString PinmemberFrancisco Manuel Suárez Grueso30-Sep-14 2:15 
GeneralBy the way. PinmemberAssorted Trailmix16-Aug-14 13:58 
GeneralRe: By the way. [modified] Pinprofessional1337Architect16-Aug-14 18:47 
GeneralRe: By the way. PinmemberAssorted Trailmix17-Aug-14 3:16 
GeneralRe: By the way. Pinprofessional1337Architect17-Aug-14 4:15 
GeneralRe: By the way. [modified] PinmemberAssorted Trailmix17-Aug-14 11:58 
GeneralRe: By the way. Pinprofessional1337Architect17-Aug-14 12:15 
GeneralRe: By the way. PinmemberAssorted Trailmix17-Aug-14 12:24 
GeneralRe: By the way. Pinprofessional1337Architect17-Aug-14 12:35 
GeneralRe: By the way. PinmemberAssorted Trailmix17-Aug-14 12:47 
GeneralRe: By the way. Pinprofessional1337Architect17-Aug-14 12:53 
GeneralRe: By the way. PinmemberAssorted Trailmix17-Aug-14 18:45 
GeneralRe: By the way. Pinprofessional1337Architect17-Aug-14 20:00 
GeneralRe: By the way. PinmemberAssorted Trailmix18-Aug-14 6:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411023.1 | Last Updated 30 Sep 2014
Article Copyright 2014 by 1337Architect
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid