Symmetric Encryption

Let's suppose that and Alice and Bob want to be able to talk to each other confidentially, so they mutually agreed on the following encryption algorithm for all the messages they send to each other: each letter of the alphabet will be replaced by the letter three places further on in the alphabet. The scheme will work cyclically, so that the letter A is regarded as following the letter Z. Case is preserved, and digits, spaces and punctuation marks are left untouched. So, for example, if Alice wants to sent this message to Bob:

 You owe me 100 dollars 

She will encrypt it first to this:

 Brx rzh ph 100 grooduv 

The original message by the way is known as plain text, and the encrypted version as cipher text. Bob knows therefore that to decrypt a message he has to apply the same algorithm, but replacing each letter by the letter three places further back (or, equivalently, 23 places further forward).

The main thing I want you to notice is that there are really two parts to this encryption scheme: the algorithm - the rule that says you shift each letter by certain number of places in the alphabet - and the number of places, in this case three. And that's an important principle: in general encryption schemes feature an algorithm, which tells you in principle how the encryption will be carried out, and a number called the key which has to be inserted into the algorithm at some point. In order either to encrypt or decrypt a message, you need to know both of these things. For example, suppose that Eve wants to eavesdrop on Alice and Bob, and she knows the algorithm but not the key. She knows that each letter gets transposed but she doesn't know about how much. The only thing she can do is start guessing keys. First she tries using a key of 1 to decrypt the message. This yields.

 Csy sai qi 100 hsppevw 

That's obviously garbage so 1 wasn't the correct value. So Eve tries 2. That gives garbage too. Eventually of course she will get to the value 23, and recover the original message. The fact that this message is not garbage tells her she now has the correct key - which also means that she can easily decrypt future messages until of course Alice and Bob change either the key or the algorithm.

I know that for our example here, there are a lot of clues in the message to assist Eve in decoding it. All the spaces separating words, and the punctuation marks, are still there and so it's not hard to start making educated guesses about some of the simple words, or to use frequency analysis on the different letters to help work out likely values. But that's only because our encryption scheme is so simple. Modern computer encryption algorithms don't give you any such clues. If you're faced with something that's encrypted with a modern algorithm, pretty much your only option is to use brute force, and start guessing different key values. So by restricting Eve to doing this, I'm keeping the example more related to real computer systems.

The reason that this algorithm is called symmetric is partly because Bob and Alice both use the same algorithm, and partly because it's very easy to work out the decryption key from the encryption one and vice versa: encryption and decryption are carried out by basically the same method. Bob encrypts messages he sends in exactly the same way that Alice does - by replacing letters. Substitute a more complex algorithm and we have a way of encrypting messages, files, programs, and so. The most well known symmetric algorithm has probably been DES (Digital Encryption Standard), which was developed in the 1970s and which encrypts data in 64-byte blocks. Other algorithms you'll hear about are Triple DES, Rijndael, RC2 and RC4.

Let's write the process a bit more mathematically. We will call the two keys P and S, and let's call the original message M. If we encode the message using P then we get some cipher text which we'll call P(M). If we then decode this using S we get S(P(M)). If everything's working properly, we know that that gives the original message back. So M = S(P(M)). We could also go the other way round - apply S first, then apply P. That will give us our original message back too. So in summary:

 S(P(M)) = P(S(M)) = M P(M) = encoded stuff that looks like garbage - you have to know what S is to decode it. S(M) = encoded stuff that looks like garbage - you have to know what P is to decode it. 

It might look like we've just gone out of our way to write the obvious using unnecessarily complex-looking algebra, but this will be more useful when we come to examine public key cryptography.

One of the principles of cryptography is that it is the key that is kept secret. The algorithm isn't secret - it's largely pointless trying to keep the choice of algorithm secret because there aren't actually very many algorithms available anyway. The requirements of a workable encryption algorithm are quite strict, and there haven't been that many algorithms developed that fit the bill.

Encrypting a Message with Managed Code

Now we've learned about the principles of symmetric encryption, we'll demonstrate how to encrypt and decrypt a message using the classes provided in the System.Security.Cryptography namespace. This namespace contains a large number of classes to perform different cryptography-related operations, including classes that will encrypt and decrypt data according to many of the standard algorithms. We're going to use the DES algorithm, which means that the class we use will be the DESCryptoServiceProvider class. This class derives from the abstract class DES. Other similar pairs of classes in the System.Cryptography namespace include MD5/MD5CrytpServiceProvider and RSA/RSACryptoServiceProvider, which respectively implement the MD5 and RSA algorithms.

For the sample we're going to write two programs to respectively encrypt and then decrypt a message using the DES encryption algorithm.

Bear in mind that, although I'm showing you how to write an encryption program, there are situations in which encryption services are automatically provided, for example using SSL to talk to IIS. Before rolling your encryption routine, do make sure that you're not wasting your time duplicating a pre-existing service that you could have used. On the other hand, one good reason for writing your own encryption program is in order to provide secure communications where you don't for example want IIS to be installed on the machines concerned.

The message we will encrypt is a file called Bloops.txt. This file arose from the fact that I dictated much of this book using speech recognition software. Some of the mistakes the software makes are sufficiently noteworthy that I've got into the habit of copying them into the Bloops.txt text file as I correct the chapters. Since the contents of the file aren't really relevant to the sample, I'll just display a small snippet from the file here. The full file is of course available with the code download on the Wrox Press web site, and the same information is available on my own site, at http://www.SimonRobinson.com/Hum_DragonBloops.aspx. The file consists of line pairs, the first pair listing what the speech recognition software wrote out, the second line indicating what I actually said:

 authentic invitation procedures authentication procedures Come into rock COM Interop 

Anyway, on to the code. First, the encryption program.

The Encryption Program

The code is a simple console application, with the encryption being done in the Main() method. There is a small helper method, WriteKeyAndIV(), which writes out the key that is used for the encryption and an initialization vector (IV - a random number used to initialize the encryption algorithm) to a file called KeyIV.txt. The reason for this file is that the encryption program works by generating a random key and vector - and obviously the decryption program needs to be able to read and therefore use the same values of these items so it can perform the decryption correctly! Storing these values in a plain text file is of course hopelessly insecure - I've just done it here to keep the example simple. In real life you'd likely use a public key encryption method to communicate the key.

The Main() method looks like this:

 static void Main(string[] args) {    DESCryptoServiceProvider des = new DESCryptoServiceProvider();    des.GenerateKey();    des.GenerateIV();    WriteKeyAndIV(des);    ICryptoTransform encryptor = des.CreateEncryptor();    FileStream inFile = new FileStream("Bloops.txt", FileMode.Open);    FileStream outFile = new FileStream("BloopsEnc.txt", FileMode.Create);    int inSize = encryptor.InputBlockSize;    int outsize = encryptor.OutputBlockSize;    byte[] inBytes = new byte[inSize];    byte[] outBytes = new byte[outSize];    int numBytesRead, numBytesOutput;    do    {       numBytesRead = inFile.Read(inBytes, 0, inSize);       if (numBytesRead == inSize)       {          numBytesOutput = encryptor.TransformBlock(inBytes, 0,                                              numBytesRead, outBytes, 0);          outFile.Write(outBytes, 0, numBytesOutput);       }       else if (numBytesRead > 0)       {          byte [] final = encryptor.TransformFinalBlock(inBytes, 0,                                                        nwnBytesRead);          outFile.Write(final, 0, final.Length);       }    } while (numBytesRead > 0);    inFile.Close();    outFile.Close(); } 

In this code we first instantiate a DESCryptoServiceProvider object, and call methods to generate a random key and initialization vector. Then we call the WriteKeyAndIV() helper method to write these quantities out to a file (we'll examine this method soon).

The following line creates an object that will actually perform the encryption:

 ICryptoTransform encryptor = des.CreateEncryptor(); 

The details of the encryptor object are hidden from us - all we know is that it implements an interface, ICryptoTransform, which actually performs the encryption. The encryption is regarded as a transform (in this case from plain text to cipher text, but the transform can go the other way too: the same interface is used for decryption) - hence the name. The reason for this architecture in which the encryptor object is accessed via an interface is that it allows the same interface to be used with other symmetric encryption algorithms. For example, had we been wanting to perform encryption using another algorithm, the Rijndael algorithm, instead, then almost the only changes we'd need to make to the source code would be to replace the first line:

 RijndaelManaged des = new RijndaelManaged (); des.GenerateKey(); des.GenerateIV(); WriteKeyAndIV(des); ICryptoTransform encryptor = des.CreateEncryptor(); 

And we'd end up with an encryptor that uses the Rijndael algorithm, but which exposes the same interface for actually performing the encryption.

The ICryptoTransform interface performs encryption via the TransformBlock() method, which takes five parameters: a byte array containing some bytes to be encrypted (transformed), the index of the first element in the array to be transformed and the number of elements to be transformed, a byte array to receive the transformed data, and an index indicating where to place this data in the output byte array. There are also two properties: InputBlockSize, which indicates how many bytes the encryptor likes to work with at a time (these will be encrypted as one unit), and OutputBlockSize, indicating how many bytes each block will be mapped to. And finally, there's a TransformFinalBlock() method, which acts like TransformBlock, except that it's intended to transform the final block of data in the message. Since the final block may be smaller than other blocks, so it's not known in advance how big the output will be, TransformFinalBlock() places the output in a byte[] return value instead of accepting a reference to an existing array. With this information we can see how the loop that performs the encryption works:

    do    {       nRead = inFile.Read(inBytes, 0, inSize);       if (nRead < inSize)       {          byte[] final = encryptor.TransformFinalBlock(inBytes, 0, nRead);          outFile.Write(final, 0, final.Length);       }       else       {          encryptor.TransformBlock(inBytes, 0, nRead, outBytes, 0);          outFile.Write(outBytes, 0, nRead);       }    }    while (nRead > 0);    inFile.Close();    outFile.Close(); } 

Note that the FileStream.Read() method returns the number of bytes actually read. This will be zero if we have reached the end of the file.

Finally, we need to examine the method that writes details of the key and initialization vector to a file. To keep things simple, I've had them write the file out in text format - slow and insecure, but it makes for easier debugging!

 static void WriteKeyAndIV(DES des) {    StreamWriter outFile = new StreamWriter(@"KeyIV.txt", false);    outFile.WriteLine(des.KeySize);    for (int i=0; i< des.KeySize/8; i++)       outFile.WriteLine(des.Key[i]);    for (int i=0; i< des.KeySize/8; i++)       outFile.WriteLine(des.IV[i]);    outFile.Close(); } 

To write out the file, we need to know the size of the key (when I ran the program it turned out to be 64 bits). It is possible to set this size, but that involves first querying the DESCryptoServiceProvider object to find out what the allowed key sizes are, so to keep things simple I just accepted the default value. The KeySize property gives the key size in bits, but the actual key and initialization vector (which will be the same size) are returned as byte arrays, so we have to divide KeySize by eight to get the size of these arrays. The fact that the Key and IV properties have been implemented as properties incidentally runs counter to recommended programming standards: it's not recommended to define properties that return arrays - though in this case, the small size of the arrays may make this more acceptable.

The Decryption Program

Now we need to examine the code for the other program in this sample - the Decrypt program that decrypts the file. It has a very similar structure to the encryption program. We'll start off by examining the helper method that reads in the file containing the key and initialization vector.

 static void ReadKeyAndIV(DES des) {    StreamReader inFile = new StreamReader(@"KeyIV.txt");    int keySize;    keySize = int.Parse(inFile.ReadLine());    byte[] key = new byte[keySize/8];    byte[] iv = new byte[keySize/8];    for (int i=0; i< des.KeySize/8; i++)       key[i] = byte.Parse(inFile.ReadLine());    for (int i=0; i< des.KeySize/8; i++)       iv[i] = byte.Parse(inFile.ReadLine());    inFile.Close();    des.KeySize = keySize;    des.Key = key;    des.IV = iv; } 

This method reads in the values from the file in the same order that the encryption program wrote them out, constructs the byte arrays, and uses the data to set the key size, key, and initialization vector of the DESCryptoServiceProvider instance.

Now for the code to decrypt the file. In the following code, I've highlighted the lines that are different from the Main() method in the encryption program.

 static void Main(string[] args) {    DESCryptoServiceProvider des = new DESCryptoServiceProvider();    ReadKeyAndIV(des);    ICryptoTransform decryptor = des.CreateDecryptor();    FileStream inFile = new FileStream(@"BloopsEnc.txt", FileMode.Open);    FileStream outFile = new FileStream(@"BloopsDec.txt", FileMode.Create);    int inSize = decryptor.InputBlockSize;    int outsize = decryptor.OutputBlockSize;    byte[] inBytes = new byte[inSize];    byte[] outBytes = new byte[outSize];    do    {       numBytesRead = inFile.Read(inBytes, 0, inSize);       if (numBytesRead == inSize)       {          numBytesOutput = decryptor.TransformBlock(inBytes, 0, numBytesRead,                                                    outBytes, 0);          outFile.Write(outBytes, 0, numBytesOutput);       }       else       {          byte [] final = decryptor.TransformFinalBlock(inBytes, 0,                                                        numBytesRead);          outFile.Write(final, 0, final.Length);       }    } while (numBytesRead > 0);    inFile.Close();    outFile.Close(); } 

As you can see, very few lines have changed. Essentially, the only differences are the names of the input and output files, and the fact that we use a method called CreateDecryptor() instead of CreateEncryptor() to return the interface reference through which we can perform the data transform. Also, we read in the key from a file, instead of generating a random one.

The similarity between the two programs demonstrates the convenience of the model in which encryption and decryption are regarded as transforms to be performed through the same interface. However, note that this model only applies to symmetric algorithms. The .NET cryptography classes that perform public-key encryption don't use this architecture, since with public-key encryption, the encryption and decryption algorithms are very different.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net