Part one covered the background, part two went into detail about cryptographic hashes. Part three covers the most easily understood type of encryption, though you wouldn't know it by its name: symmetric encryption.
Symmetric Cryptographic Algorithms
Sometimes referred to as "shared secret" cryptography, Symmetric cryptographic algorithms encrypt and decrypt data using a trivial key. Its name comes from the fact that encryption and decryption both rely upon the same key.
A common example of symmetric cryptography in action is the common zip file. You have the option to add a password to the zip file when creating it. You must, in turn, provide the same password when the file is decrypted. In order for your secret stash to stay hidden from your mom's eyes, you have to keep the key secret. For example, you shouldn't write it on a post-it note pasted to your monitor.
Types of Symmetric Algorithms
There are five different types of symmetric algorithms available in System.Security.Cryptography. They are DES (Data Encryption Standard), TripleDES, RC2, and Rijndael/AES (Advanced Encryption Standard).
DES and TripleDES
DES is an older standard, originating from government requirements back in the 70's. Since it was designed for the computing power available at that time, it is considered very weak today. It can be brute-force cracked in under a day's worth of computation at today's power. DES has been superceded by both TripleDES, which runs data through the DES encryption algorithm three times (why not QuadDES? I dunno.), and Rijndael/AES.
RC2
RC2 was a follow-up to DES developed for Lotus Notes with the goal of being more secure but legal for export overseas. There are also more secure versions with longer keys, but they are all called RC2 in .NET; only the key length is changed (this isn't exactly true; research it if you want more info). RC2 is most famous for being the encryption used by WEP (with a 40 bit key). If you're running your wireless access point with WEP, somebody is right now connected to your network chatting up 13 year olds on MySpace. You should go home immediately, rip out your router by the power cord, and slam it into the wall. RC2 in its default form is NOT considered secure and should not be used when better alternatives are available. WEP2, by the way, uses AES for encryption.
Rijndael/AES
Rijndael is considered the most secure of the crop of symmetric algorithms available to .NET programs. I'm not sure what the differences between them are, but supposedly AES is the official, NSA approved version of Rijndael. They are both different enough to be implemented separately. If you have a choice among all symmetric algorithms, you should always use one or the other as they are the most secure and are projected to be secure for another 20 or so years.
Initialization Vectors and You
Symmetric algorithms introduce a new requirement that must be understood: The Initialization Vector, or IV. Very simply, the symmetric algorithm must start somewhere, and that is the job of the IV. Symmetric algorithms usually use "block chaining" to add an additional layer of randomness to the cypher. This is done by logically XOR-ing the current block with the previously processed block. This process is reversible, so it works well and is fast. However, to fully encrypt the first block of text you need a previous block to XOR it with. That is the purpose of the IV, to be the starting point of this process. Because it does impact the rest of the cypher, the same IV must be used to decrypt as was used to encrypt. Usually, in order to avoid having to memorize a random series of bytes along with your password, the IV is generated from the password itself.
CODE ALREADY!
The pseudocode for encrypting is:
- Convert the string to encrypt into a byte array
- Generate our IV from our password
- Create our encryption provider (Rijndael in this example)
- Get an encryptor using our password and initialization vector
- Encrypt our string to encrypt
- Return the encrypted data as an encoded string
The pseudocode for decrypting is:
- Decode our encrypted data into a byte array
- Create our provider with the same configuration
- Generate an IV from the given password
- Get a decryptor using our password and IV
- Decrypt the encrypted bytes
- Convert the byte array into a string
A Word About IV Generation
The current, cryptographically secure, deterministic and non obsolete way to generate our IV from our password is to use the class Rfc2898DeriveBytes. This is a replacement for the PasswordDeriveBytes class that you'll see in most examples on the net. This class takes a password and a salt and provides a method called GetBytes which provides an easy way to get your key and IV for use by your symmetric algorithm provider. If you use the same password and same salt, you are guaranteed to get the same bytes, and these bytes are generated in a secure manner.
Generally, you don't want to require users to remember a random salt along with their password, so its best to base it off of your password. In this example, I use the following method, which will generate a salt that's different for each password, but is always the same for the same password:
private byte[] GenerateSalt(string password)
{
return Encoding.Default.GetBytes(
"A salt must always be the same"
+ "for Rfc2898DeriveBytes to give you"
+ "the same password bytes and IV for"
+ "each password, or in this case: "
+ password);
}
Encrypting
The encryption method demonstrates how to use the Rfc2898DeriveBytes class to generate a key and IV from your password and salt:
private string Encrypt(string password,
string toEncrypt)
{
Rijndael rinedal = null;
byte[] toEncBytes = null;
try
{
toEncBytes = Encoding.Default
.GetBytes(toEncrypt);
Rfc2898DeriveBytes pdb = new
Rfc2898DeriveBytes(
password,
GenerateSalt(password));
rinedal = Rijndael.Create();
rinedal.Padding = PaddingMode.ISO10126;
ICryptoTransform tx = rinedal
.CreateEncryptor(
pdb.GetBytes(32), // our key
pdb.GetBytes(16)); // our IV
byte[] encrypted =
tx.TransformFinalBlock(
toEncBytes,
0,
toEncBytes.Length);
return Convert.ToBase64String(encrypted);
}
finally
{
// this clears out any secret data
if (rinedal != null)
rinedal.Clear();
// zeroes out our array
if (toEncBytes != null)
for (int i = 0; i < toEncBytes.Length;
i++)
toEncBytes[i] = 0;
}
}
We use the default system encoding to convert the text to a byte array. Next, we use our GenerateSalt method, along with the given password, to instantiate our Rfc2898blahblah object. We get the implementation of the Rijndael algorithm configured on the system next, and specify what type of padding mode to use.
Symmetric algorithms work on fixed sizes of data, or blocks, so when data doesn't fill up a block the remaining space must be padded somehow. Default padding modes may differ between systems, so being specific here is better than not.
Next, we get our encryptor from the provider, passing in bytes for our key and initialization vector. Key and IV lengths must be certain lengths, depending on the flavor of algorithm you are using. For instance, Rijndael allows for a maximum key length of 256 bits, or 32 bytes. Consult the docs for your algorithm to find out what sizes are acceptable.
Once encrypted, we convert the encrypted byte array into a string that can be easily stored and retrieved. And in our finally block we do a little house cleaning, sweeping up all our secret data (or as much as we can do easily).
Decrypting
The decryption method is just the reverse of the previous:
private string Decrypt(string password, string toDecrypt)
{
Rijndael rinedal = null;
byte[] toDecBytes = Convert
.FromBase64String(toDecrypt);
try
{
Rfc2898DeriveBytes pdb = new
Rfc2898DeriveBytes(
password,
GenerateSalt(password));
rinedal = Rijndael.Create();
rinedal.Padding = PaddingMode.ISO10126;
ICryptoTransform tx = rinedal
.CreateDecryptor(
pdb.GetBytes(32),
pdb.GetBytes(16));
byte[] decrypted = tx.TransformFinalBlock(
toDecBytes, 0, toDecBytes.Length);
return Encoding.Default.GetString(decrypted);
}
finally
{
if (rinedal != null)
rinedal.Clear();
}
}
Summary
Symmetric cryptographic algorithms use a key, commonly referred to as a password, to encrypt and decrypt data. There are lots of different versions available, but Rijndael and AES are the current industry standards. You'll need to generate an initialization vector (IV) using the Rfc2898DeriveBytes object. The bytes you use as the key and IV must be the correct length for the algorithm you use.
Part Four Preview
In part four we go over (or maybe just start to go over; its pretty complex) asymmetric cryptographic algorithms. These use public/private key pairs to perform one-way encrypting and data signing, and is some pretty heavy and useful stuff. Could somebody else write that one for me, please? This stuff is getting complex!