Click here to Skip to main content
15,868,292 members
Articles / Security / Cryptography

Swanky Encryption/Decryption in C#

Rate me:
Please Sign up or sign in to vote.
4.59/5 (63 votes)
26 Jan 2015MIT13 min read 186.8K   3.9K   146   105
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.

For those who are looking for a quick solution this is the basically the code you need to encrypt and decrypt data. And there is a lot of other examples in the downloadable code.

C#
string enc = CryptoHelper.EncryptBase64String("password", AutoSaltSizes.Salt512, "data");
string dec = CryptoHelper.DecryptBase64String("password", AutoSaltSizes.Salt512, enc);

My implementation is a class library with useful functions to do one liner encryption 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 your end other than deciding how big the salt value should be. 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 which will help with the more complex scenarios.

Background

The reason I started this article is because I started looking into encryption with CryptoStream and it was useless for what I needed to achieve 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 and a growing interest 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, it will definetly make your life easier so you can focus on the quality of your code. Here is a few things I want to highlight

  • 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 secure way so you don't have to bother about it.
  • You still have the the flexibility to use all available crypto settings 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 simple 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.

Image 1

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.

C#
public enum SymmetricCryptoAlgorithm
{
	AES_128_CBC,
	AES_128_ECB,
	AES_128_CFB,
	AES_192_CBC,
	AES_192_ECB,
	AES_256_CBC,
	AES_256_ECB,
	AES_256_CFB,

	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
}

You also get the option of selecting Padding mode, I picked PKCS7 as the default padding since it seems to be that way for

C#
public enum PaddingMode
{
	None,
	PKCS7,
	Zeros,
	ANSIX923,
	ISO10126
}

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.

C#
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.

C#
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.

C#
// 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 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.

C#
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.

C#
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.

Image 2

Performance

Here is a comparison between the different crypto algorithms encrypting/decrypting exactly the same data on an Intel i7-3770 CPU. The way you read the labels in the x axis is ALGORITHM_KEYSIZE_CIPHERMODE_BLOCKSIZE, the blocksize part is only there for Rijndael because it's the only algorithm with the option to modify it).

Looking at a comparison of all algorithms 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.

AES

Image 3

Looking closer at AES it's interesting to see that the CFB cipher mode does not perform very well with Hardware accelerated AES. I can only assume this mode is not supported with 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. I assume if you have older hardware, you might get accelerated RC2 and maybe DES & T-DES. I have not been able to confirm this yet.

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.

Because of the good performance and becaue AES is the "new" FIPS standard from November 26, 2001 you should probably always go for AES_256_CBC. This will ensure good security and performance. Some argue that Rijndael is the best one but in fact AES is a subset of Rijndael and it just supports higher blocksizes, if this means better encryption I can't say, but I can't justify the use of it because of the lack of hardware acceleration support. The only reason to use anything else than AES is for compability reasons.

Here follows the reult of performance testing for all symmetric algorithms.

Rijndael

Image 4

DES

Image 5

Triple DES

Image 6

RC2

Image 7

 

Points of Interest

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

I have implemented salt for encrypted data in this crypto lib which I call AutoSalt, salt is however more commonly used when storing passwords for user accounts.

If you ever built an application which handles useraccounts and storing credentials, you probably came across salt if you implemented it properly. Hopefully you 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 also use a salt value.

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.

C#
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 you can choose the padding you want, but unless you have a reason to change the padding I would stick to the default padding which is PKCS7.

So far I havent found a reason to change it myself except when you set the padding mode to None. In this case you need to handle the padding yourself in the data you feed to the cipher. It only accepts data which is a multiple of the blocksize. This is only useful for really advanced users.

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


Written By
Architect
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: By the way. Pin
1337Architect17-Aug-14 19:00
professional1337Architect17-Aug-14 19:00 
GeneralRe: By the way. Pin
Assorted Trailmix18-Aug-14 5:25
Assorted Trailmix18-Aug-14 5:25 
GeneralRe: By the way. PinPopular
1337Architect18-Aug-14 9:16
professional1337Architect18-Aug-14 9:16 
GeneralRe: By the way. PinPopular
Kent K22-Aug-14 3:35
professionalKent K22-Aug-14 3:35 
GeneralRe: By the way. Pin
Assorted Trailmix2-Sep-14 10:59
Assorted Trailmix2-Sep-14 10:59 
GeneralRe: By the way. PinPopular
SteveHolle20-Aug-14 11:52
SteveHolle20-Aug-14 11:52 
GeneralRe: By the way. Pin
Assorted Trailmix20-Aug-14 13:34
Assorted Trailmix20-Aug-14 13:34 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Aug-14 1:38
professionalȘtefan-Mihai MOGA13-Aug-14 1:38 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
GeneralRe: My vote of 5 Pin
1337Architect15-Aug-14 6:16
professional1337Architect15-Aug-14 6:16 
GeneralMy vote of 3 Pin
sjelen11-Aug-14 3:56
professionalsjelen11-Aug-14 3:56 
GeneralRe: My vote of 3 Pin
1337Architect15-Aug-14 6:23
professional1337Architect15-Aug-14 6:23 
GeneralMy vote of 1 Pin
Assorted Trailmix4-Aug-14 22:47
Assorted Trailmix4-Aug-14 22:47 
GeneralRe: My vote of 1 Pin
1337Architect6-Aug-14 10:26
professional1337Architect6-Aug-14 10:26 
GeneralRe: My vote of 1 Pin
Assorted Trailmix6-Aug-14 16:22
Assorted Trailmix6-Aug-14 16:22 
GeneralRe: My vote of 1 Pin
1337Architect7-Aug-14 9:41
professional1337Architect7-Aug-14 9:41 
GeneralRe: My vote of 1 Pin
Assorted Trailmix7-Aug-14 19:34
Assorted Trailmix7-Aug-14 19:34 
GeneralRe: My vote of 1 Pin
1337Architect15-Aug-14 6:15
professional1337Architect15-Aug-14 6:15 
GeneralRe: My vote of 1 Pin
Assorted Trailmix16-Aug-14 12:46
Assorted Trailmix16-Aug-14 12:46 
GeneralRe: My vote of 1 Pin
Assorted Trailmix16-Aug-14 13:00
Assorted Trailmix16-Aug-14 13:00 
GeneralRe: My vote of 1 Pin
1337Architect16-Aug-14 17:50
professional1337Architect16-Aug-14 17:50 
GeneralRe: My vote of 1 Pin
Assorted Trailmix17-Aug-14 2:51
Assorted Trailmix17-Aug-14 2:51 
GeneralRe: My vote of 1 Pin
1337Architect17-Aug-14 3:19
professional1337Architect17-Aug-14 3:19 
GeneralRe: My vote of 1 Pin
Assorted Trailmix17-Aug-14 11:13
Assorted Trailmix17-Aug-14 11:13 
GeneralRe: My vote of 1 Pin
1337Architect17-Aug-14 11:17
professional1337Architect17-Aug-14 11:17 
GeneralRe: My vote of 1 Pin
Assorted Trailmix17-Aug-14 11:29
Assorted Trailmix17-Aug-14 11:29 

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

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