Ruminations of J.net idle rants and ramblings of a code monkey

Notes on Symmetric Cryptography

.NET Stuff | Security

Howdy y'all.  Me again.  I've gotten a lot of questions about doing crypto in .Net ... for some reason, it's been something that interests me quite a bit.  Now, there are a bunch of resources out there on this, but it's (apparently) not always easy to find.  So, I'm going to put some tips and thoughts here. 

First, let me say this: .Net has awesome support for crypto. This support is in the System.Security.Cryptography namespace (or a sub-namespace under that) with most of the classes implemented in mscorlib.   I'm going to focus on symmetric encryption here (I'll deal with the others later) Symmetric encryption is reversable (you can get the clear text from the crypto text) and is based on a single key. There are several symmetric algorithms included with .Net, and all of their implementation classes derive from System.Security.Cryptography.SymmetricAlgorithm abstract class:

  • DES (Data Encryption Standard) (FX 1.0+) : This was the Federal Information Processing Standard (FIPS) starting in 1976. It has a 56-bit key, so with today's modern computers, it is subject to a brute-force attack in a trivial amount of time. It's not recommended for general usage anymore, but it has been so widely used for so long that it's not wise to not include it.
  • TripleDES (FX 1.0+): Also commonly referred to a 3DES.  Basically, as it's name implies, it's DES 3 times over.  There are (usually) 3 DES keys and the cipher is run through the three keys on successive passes.  There are actually several variations on the theme that are out and about, some using 2 keys, some using 1 key but, in general, the most common method is three keys. 
  • Rijndael (FX 1.0+): This was the algorithm that as become the Advanced Encryption Standard (AES) and is the replacement for 3DES.  It supports 128, 192 and 256 bit keys. To put this in perspective, if a machine could recover a DES key in a second (using brute force), it would take approximately 149 trillion years to crack a 128-bit AES key (see http://csrc.nist.gov/archive/aes/index.html)  It was the finalist in an exhaustive analysis process by the National Institues of Standards and Technology (NIST) with input from the US National Security Agency (or No Such Agency, depending on your viewpoint) to determine the next FIPS algorithm.  It was selected for its high level of security as well as it's efficiency on modern processors (DES and 3DES were notoriously inefficient).  The other algorithms were considered secure enough for non-classified information, but only Rijndael was considered secure enough for classified information.  For details on the algorithm, see http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf.  Now, if you understand that stuff, let me know.  Perhaps you could explain it to me in English.
  • AES (Fx 3.5): This is a FIPS-certified implementation of Rijndael.  And yes, this is a big deal, especially for organizations that deal with the US government and, particularly, the DoD. For classified information, the key must be 192 or 256 bits.

Now, because the all derive from the same base class, using them is pretty much the same (with the exception of key sizes).  Here's a code sample (with comments):

public static byte[] EncryptText(string clearText)
{
//Create our algorithm. using (SymmetricAlgorithm alg = Rijndael.Create())
{
//Can also use: //SymmetricAlgorithm alg = SymmetricAlgorithm.Create("Rijndael");//For clarity, we'll generate the key.//In the real world, you'll likely get this from ... somewhere ... 
alg.GenerateKey();//An initialization vector is important for true security of the algorithm. 
alg.GenerateIV();//Create our output stream for the cipherText//We're using a memory stream here, but you can use //any writable stream (i.e. FileStream)
System.IO.Stream outputStream = new System.IO.MemoryStream();//Create the crypto stream that the algorithm will use.
CryptoStream crypStream = new CryptoStream(outputStream,
alg.CreateEncryptor(), CryptoStreamMode.Write);//Now we need to read from the stream//This will be a stream reader that reads from the crypto stream. //This writer will write to the CryptoStreamusing (System.IO.StreamWriter inputWriter = new System.IO.StreamWriter(crypStream))
{
//Write to the stream writer ... this writes to the underlying CryptoStream
inputWriter.Write(clearText);//Not usually necessary, but just to be sure. 
inputWriter.Flush();
}
//The encrypted data is now ready to read. //If we were using, say, a FileStream for the output, we wouldn't need to do this. //Create a binary reader to read it into a byte array.using (System.IO.BinaryReader outputReader = new System.IO.BinaryReader(outputStream))
{
//Read the bytes.
byte[] cipherBytes = new byte[outputStream.Length];int dataRead = outputReader.Read(cipherBytes, 0, cipherBytes.Length);
}
//Make sure we close the other streams. 
outputStream.Close();
crypStream.Close();//... and return ... return cipherBytes;
}
}

So ... the comments do tell a lot of the story, but not all.  What is an IV?  No, it's not a needle ... it's an initialization vector.  This is an extra bit of random gobbledygook that is added into the beginning of the clear text before it is run through the cipher.  This is actually very important to do.  You see, these algorithms are block ciphers, meaning that they encrypt blocks at a time.  By default, they are done in CipherBlockChaining (CBC) mode, where some of the previous block of cipher text is fed into the next block.  This helps increase the randomness of the cipher text.  However, if the clear text starts with the same pattern (not very uncommon), then the beginning of the cipher text will also be the same.  Not good as it helps a bad guy reduce the key space.  So ... the IV prevents that from happening. You can store the IV separately from the cipher text (in the clear ... bad guys can't get anything useful from it) or you can prepend the returned cipher text with it (so the return is [IV][cipher text]).  I prefer the second ... it's a touch of security by obscurity (this isn't bad as long as it's not the only thing that you rely on ... it can be a part of a complete defense-in-depth strategy).

Decrypting is very similar ... the same process (and almost the same code) in reverse.  Here's a sample with less comments (many of the comments above also apply here).

public static string DecryptText(byte[] cipherText)
{
using (SymmetricAlgorithm alg = Rijndael.Create())
{
//These will come from somewhere. 
alg.Key = ourKey;
alg.IV = ourIV; 
System.IO.Stream outputStream = new System.IO.MemoryStream();//Create the crypto stream. //This is the biggest difference between encryption and decryption. 
CryptoStream crypStream = new CryptoStream(outputStream,
alg.CreateEncryptor(), CryptoStreamMode.Read);//Also a slightly different write because we have bytes to write. using (System.IO.BinaryWriter inputWriter = new System.IO.BinaryWriter(crypStream))
{
inputWriter.Write(cipherText); 
inputWriter.Flush();
}
//The decrypted data is now ready to read. //If we were using, say, a FileStream for the output, we wouldn't need to do this. //Create a binary reader to read it into a byte array.string clearText;using (System.IO.StringReader outputReader = new System.IO.StringReader(outputStream))
{
clearText = outputReader.ReadToEnd(); 
}
//Make sure we close the other streams. 
outputStream.Close();
crypStream.Close();//... and return ... return clearText;
}
}

Some final comments:

  • I like to have all of the disposable objects in their own using blocks.  I didn't do that here to minimize the nesting of the using blocks for the sake of clarity.  That said, I'm a big fan of using blocks.  That's my story and I'm sticking to it.
  • I didn't talk about key storage.  That's the stickiest part of using symmetric algorithms.  I'll deal with that in a later post. Here's a clue: DPAPI.
  • If you notice, the encrypt and the decrypt functions are almost identical.  Yes, it is possible to have both operations in one function, with a bool indicating encryption/decryption. I, personally, like to do this.  I did not do that here for the sake of clarity and a clear separation between the two processes.  I'm sure you can look at the samples above and make that happen.
  • You can store the byte arrays as text/string.  To do that, use this snippet: string cipherString = System.Convert.ToBase64String(data);
  • This really is pretty easy.  It's very straightforward.  If you think it's hard, try reading the documentation for the Win32 CryptoAPI.  It's called the CryptoAPI that because it's cryptic.  It will make your brain hurt.  Badly.  I recommend a heavy dose of Advil after reading it.  You'll need it.
  • Use one of these algorithms.  I do prefer Rijndael/AES, but any of these (even DES) is better than creating your own "crypto algorithm".  In the words of Michael Howard, that's craptography.  Just say no.  Don't do it.  Unless you are a PhD in Mathematics specializing in crypto algorithms, you'll get it wrong. Read the Rijndael article referenced above. If you can't understand it ... don't write your own algorithm.  It's just that simple.  Even if you do understand it, it's still not a good idea to write your own algorithm.  Just use Rijndael. It's been well vetted and just because the algorithm is known doesn't mean that it's less secure.  On characteric of a good algorithm is that the algorithm details can be public without compromising the security of the algorithm.

Comments (1) -

andrei 5/8/2009 4:43:43 PM Romania #
andrei

excellent article thank you so much.

im interested in these parts in your DecryptText method:

  //These will come from somewhere.
        alg.Key = ourKey;
        alg.IV = ourIV;

How could I retrieve this exactly? I mean, my encryption method generates a random initialization vector and key when I write: Rijndael.Create()

Of course in my decryption method I cannot retrieve that exact algorithm, but a new random one will be generated... so how can I store the initial algorithm to retrieve it in the decrypt method?

thank you for any idea, in advance
cheers