Using Encryption in .NET

Encrypting & Decrypting

ICryptoTransform

You'll get to be good friends with this interface as you put encryption to work in your applications. ICryptoTransform is the standard interface through which you ask the cipher to encrypt and decrypt data. Note that this interface is also used by other cryptographic classes, such as hashes. Let's have a quick look at the members of this interface.

TransformBlock – Call this method to transform (encrypt or decrypt) 1 or more blocks before the end of the message.

TransformFinalBlock – Call this method to transform 1 or more blocks at the end of the message. Given our discussion of padding, you should understand why it is necessary to transform blocks at the end of the message differently than those before the end.

CanReuseTransform – Returns a boolean indicating whether the current transform can be reused. In the case of the FCL ciphers, this will always return true.

CanTransformMultipleBlocks – Returns a boolean indicating whether multiple blocks can be transformed in a single call to either TransformBlock or TransformFinalBlock. In the case of the FCL ciphers, this property also always returns true, indicating that you need not add logic to your code to process individual blocks of data. The one caveat, of course, is that you must call the appropriate transform depending on whether the data is at the end of the message (TransformFinalBlock) or not (TransformBlock).

InputBlockSize/OutputBlockSize – These properties return an integer indicating the input and output block sizes, respectively. In the case of ciphers, these will always be the same, and their return value will be dependent upon the value you specified in the BlockSize property of the cipher.

Encrypting and Decrypting

We now have enough background to begin encrypting and decrypting data. To perform the actual transformation of Encryption or Decryption, you need to create an instance of what is called a Transform class. You do this in .NET by calling the cipher's CreateEncryptor or CreateDecryptor methods, depending, of course, on whether you're encrypting data or decrypting data. The CreateEncryptor/Decryptor methods return an ICryptoTransform interface to the newly created transform class. Once you have the ICryptoTransform interface, you simply need to call the TransformBlock and TransformFinalBlock methods as appropriate. Let's add two new methods to our CipherWrapper class: EncryptMessage and DecryptMessage.

public byte[] EncryptMessage(byte[] plainText)
{
  ICryptoTransform transform = _cipher.CreateEncryptor();
  byte[] cipherText = transform.TransformFinalBlock(plainText, 0, plainText.Length);
  return cipherText;
}
public byte[] DecryptMessage(byte[] cipherText)
{
  ICryptoTransform transform = _cipher.CreateDecryptor();
  byte[] plainText = transform.TransformFinalBlock(cipherText, 0, cipherText.Length);
  return plainText;
}

Note that the only difference between the two functions is the call to either CreateEncryptor or CreateDecryptor. These two methods return an ICryptoTransform interface to a new transform class which actually performs the correct cryptographic transformations depending on the encryption mode, Encrypt or Decrypt. It is necessary to return a new class instance, because the transformation must maintain state in the case of transformation performed in multiple steps.

The example above assumes that we're using a message-based protocol where we know the entire contents of either the plain or cipher text at encryption/decryption time. Since we also know that the FCL ciphers always return true from CanTransformMultipleBlocks, we need not call TransformBlock method at all. We can simply transform the entire message in one go by calling TransformFinalBlock. If you are implementing a system where you may not know the entire message, you will have to modify this simple example to call TransformBlock/TransformFinalBlock as appropriate. It's not terribly difficult to use the TransformBlock method. Basically, the only difference from a coding standpoint is that it does not return any data. Instead, you must pass in a buffer, into which the cipher will place the transformed bytes, as well as an offset into the buffer where you would like the data to be stored.

Crypto Streams

For stream-based protocols, it would be nice to have built-in functionality to allow us to read and write individual bytes at a time or at least to write chunks not broken into perfect blocks. Fortunately, the FCL provides this functionality in the CryptoStream class. I don't want to clutter the article with code that is specific to streaming rather than cryptography, so I'll just provide a couple of examples that demonstrate the basics of using the CryptoStream class.

byte[] EncryptMessageUsingStream(byte[] plainText)
{
  ICryptoTransform transform = _cipher.CreateEncryptor();
  MemoryStream ms = new MemoryStream();
  CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write);
  cs.Write(plainText, 0, plainText.Length);
  // Remember padding!  This instructs the transform to pad and finish.
  cs.FlushFinalBlock();
  byte[] cipherText = ms.ToArray();
  return cipherText;
}

Notes: In order to implement this in a stream-based protocol, you would break this method up into possibly several methods that initialize the stream once, then call CryptoStream.Write in iterations, then call FlushFinalBlock and read the data.

byte[] DecryptMessageUsingStream(byte[] cipherText)
{
  ICryptoTransform transform = _cipher.CreateDecryptor();
  MemoryStream ms = new MemoryStream(cipherText);
  CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read);
  byte[] plainTextBuffer = new byte[cipherText.Length];
  // Remember padding!  The length read may be different than the
  // length of the plain text.
  int plainTextLength = cs.Read(plainTextBuffer, 0, cipherText.Length);
  byte[] plainText = new byte[plainTextLength];
  Array.Copy(plainTextBuffer, 0, plainText, 0, plainTextLength);
  return plainText;
}

Notes: The notes on EncryptMessageUsingStream also apply here. The reason that the length of the cipher text and the actual number of bytes that are read from the stream are different is, of course, padding. You don't necessarily have to allocate a temporary buffer, as I have done. You could simply return the entire plainTextBuffer with an integer indicating the length of the actual plain text, which is returned from the CryptoStream.Read call. Alternatively, if you know the length of the plain text, you could pass this as a parameter and only allocate and read the appropriate length.

You might also like...

Comments

About the author

Steve Johnson United States

Steve is a senior developer and consultant for 3t Systems, Inc. in Denver, CO, where he spends his days doing architecture, training, and "in-the-trenches" development. Steve began programming W...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Engineers are all basically high-functioning autistics who have no idea how normal people do stuff.” - Cory Doctorow