United KingdomHome Page  
 

Conventional Cryptography in the .NET Framework

Simon Thorneycroft 2002

Introduction

In general, key based cryptographic algorithms can be divided into two types: symmetric (also known as conventional algorithms) and asymmetric (more commonly referred to as public key algorithms).

The symmetric algorithms are so called because the same key that is used for the encryption stage is also used in the decryption stage.  Conversely, in an asymmetric algorithm, different – but intimately related - keys are used for the encryption stage and the decryption stage.

The type of Cryptography chosen depends on the reason for its use. Confidentiality (protecting data from prying eyes) is the first application that springs to mind, however, cryptography can also be used for authentication, integrity and non-repudiation.  We can use cryptography to show a message is from the person it purports to be from (authentication), that it has not been tampered with since it was written (integrity) and it is then impossible for the sender to deny that he sent the message (Non-repudiation).

Symmetric cryptographical techniques are most often applied to the first problem, that of confidentiality.  A message encrypted with a symmetric key should be unreadable by anyone who does not hold the same key.  Much like a briefcase with a combination lock, if you don’t know the combination, you cannot get at the contents.  The keys that are chosen for the algorithms can vary widely in size and the sum collection of all the possible keys is referred to as the keyspace.

Cryptographic algorithms can be run in a number of cipher modes, these modes are chosen based on their applicability to the encryption task in hand.  Perhaps the simplest of these modes is Electronic Code Book mode (ECB), in this mode a block of plain text would always encrypt to the same ciphertext. 

It is easy to see why the use of ECB may be undesirable; if the length of the plaintext to be encrypted was sufficiently long and contained repeated sequences a cryptanalyst (someone wishing to break the encryption and read the message), could build a book of blocks that represented all the possible plaintext blocks and their encrypted counterparts.  This is not an easy undertaking however, as a separate codebook would have to be built for each key in the keyspace (even for a 64 bit key there are 2^64 different keys and for a block size of 64 bits then each code book would have 2^64 entries).

More worrying however is the fact that a message could be altered.  Substituting or even deleting encrypted blocks from the ciphertext can change the message considerably with no evidence of tampering.  ‘Balance of account X is –5000 GBP’ could become ‘Balance of account X is +5000 GBP’ by placing known blocks into the stream!

Other cipher modes may be used, such as: cipher block chaining (CBC), cipher feedback (CFB), output feedback (OFB).  In these modes the same piece of plaintext elsewhere in the stream will encrypt to different ciphertext in the output.  For example, in CBC mode, the ciphertext from the previous block of encrypted plaintext is XORed with the plaintext for the current block before encrypting.  This leaves the problem of what to do with the first block of plaintext.  CBC solves this by using an initialisation vector (IV); this block can be any random data and is used to XOR the first block of plaintext. 

Since many of these modes operate on blocks that are in excess of 1 byte, the encrypted plaintext will be of a different length to the plaintext itself.  This has obvious implications for the final block of plaintext; it must be padded to the block length.  This can be done with random data, but more often ciphertext stealing is performed (CTS, using some of the encrypted plaintext to pad the final block) or alternatively the size of the final block is appended with some random data so that the decryption process knows what is real plaintext and what is padding.

Symmetric Cryptography in .NET

The .NET framework class library contains a number of classes to make the use of cryptography within your applications simple.  These classes are located in the System.Security.Cryptography namespace (two further namespaces within this namespace provide support for X509 certificates and the XML encryption standard).

The base class for all the symmetric algorithms is not surprisingly named SymmetricAlgorithm; this abstract class has four subclasses, one for each of the symmetric algorithms that are currently supported by the class library.  They are DES, TripleDES (a variant of the DES algorithm), RC2 and Rijndael.  The DES class and its siblings are not implementations of the cryptographic algorithms however, they too are abstract classes; implementing the actual algorithms is left to the DESCryptoServiceProvider, TripleDESCryptoServiceProvider, RC2CryptoServiceProvider and RijndaelManaged classes.  The implementation classes provide a wrapper around the Cryptographic service provider familiar to those who have programmed using the Microsoft CryptoAPI.  You cannot inherit from the implementation classes for use in your applications (the classes are sealed); they must be used as is.

Okay, now that we know what the classes are, let’s take a quick look at the public methods and properties they provide.

SymmetricAlgorithm Public Properties

BlockSize

Gets/ sets the block size of the cryptographic operation in bits.

FeedbackSize

Gets /sets the feedback size of the cryptographic operation in bits.

IV

Gets / sets the initialization vector (IV) for the symmetric algorithm.

Key

Gets/sets the secret key for the symmetric algorithm.

KeySize

Gets / sets the size of the secret key used by the symmetric algorithm in bits.

LegalBlockSizes

Gets the block sizes that are supported by the symmetric algorithm.

LegalKeySizes

Gets the key sizes that are supported by the symmetric algorithm.

Mode

Gets or sets the mode for operation of the symmetric algorithm.

Padding

Gets or sets the padding mode used in the symmetric algorithm.

 

SymmetricAlgorithm Public Methods

Clear

Releases all resources used by the SymmetricAlgorithm.

Create

Overloaded. Creates an instance of a cryptographic object used to perform the symmetric algorithm.

CreateDecryptor

Overloaded. Creates a symmetric decryptor object.

CreateEncryptor

Overloaded. Creates a symmetric encryptor object.

GenerateIV

When overridden in a derived class, generates a random initialization vector (IV) to be used for the algorithm.

GenerateKey

When overridden in a derived class, generates a random Key to be used for the algorithm.

ValidKeySize

Determines whether the specified key size is valid for the current algorithm.

 

The two most important properties are key and IV (the initialisation vector), you could set just these two properties and then use the class.  To aid you in setting these values, the class provides information on legal key sizes; the LegalKeySizes property is an array of KeySizes classes.  The KeySizes class provides a minimum key size, a maximum key size and a legal increment (called the skip size).  A similar situation exists with the block size property  (here the LegalBlockSizes property is an array of BlockSizes classes). 

Interestingly, when you try to set the key, a check is performed to see if the key belongs to a set of weak keys for the algorithm in question, these are keys that have been found to be significantly easier to break than other keys in the keyspace.  If you choose one of these keys then a Cryptographic exception is thrown.  Trying to set either the IV or the key to an invalid size will also raise an exception.  If you do not wish to choose a key yourself then you don’t have to.  When you use the class, it will call the GenerateKey and GenerateIV methods for you, but make sure you store these values, otherwise you won’t be able to decrypt the ciphertext.

Once you have the class set up to your satisfaction, you can call one of two methods that return an ICryptoTransform interface, they are CreateEncryptor and CreateDecryptor respectively.  These methods are overloaded to allow you to specify the key and initialisation vector at the same time if you wish.

Because it is likely that you will wish to encrypt or decrypt entire files or perhaps a stream of bytes from a network connection, the framework class library provides a CryptoStream class that inherits from System.IO.Stream, allowing you to call all the methods that you could on any other stream.  A couple of words of caution though.  Firstly, when encrypting, make sure you call Close on your Cryptostream instance otherwise the member method FlushFinalBlock (which writes the padded final block), will never be called and the decryption step will fail.  Secondly, when decrypting, do not keep reading from the stream until you have read the same number of bytes as the file size, this will also fail.  The final block is probably padded and therefore when decrypted will be smaller than the actual file size.

Okay, lets sum up with two pieces of C#, one for encrypting and the other for decrypting:

Code Listing One: Encrypting the file

SymmetricAlgorithm saAlgorithm = new TripleDESCryptoServiceProvider();

ICryptoTransform Transform;

FileStream fsInFile = new FileStream      (    

      sPlaintextFile,

      FileMode.Open,

      FileAccess.Read,

      FileShare.Read

      );

FileStream fsOutFile = new FileStream      (

      sEncryptedFile,

      FileMode.Create,

      FileAccess.Write,

      FileShare.None

      );

fsOutFile.SetLength(0);

Byte[] byteKey = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

Byte[] byteIV = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

saAlgorithm.Mode = CipherMode.CBC;

Transform = saAlgorithm.CreateEncryptor(byteKey,byteIV);

CryptoStream cs = new CryptoStream(fsOutFile,Transform,CryptoStreamMode.Write);

long lFileLen = fsInFile.Length;

int iProcessed = 0;

Byte[] byteBuffer = new Byte[8];

int iRead;

while (iProcessed < lFileLen)

{

      //read from the in file and write to the out file.

      iRead = fsInFile.Read(byteBuffer,0,8);

      cs.Write(byteBuffer,0,iRead);

      iProcessed += iRead;

      Console.WriteLine("Processed {0} of {1}",iProcessed,lFileLen);

}

cs.Close();             // calls cs.FlushFinalBlock();

fsOutFile.Close();

fsInFile.Close();

 

Code Listing Two:  Decrypting the file

SymmetricAlgorithm saAlgorithm = new TripleDESCryptoServiceProvider();

ICryptoTransform Transform;

FileStream fsInFile = new FileStream      (    

      sEncryptedFile,

      FileMode.Open,

      FileAccess.Read,

      FileShare.Read

      );

FileStream fsOutFile = new FileStream      (

      sPlaintextFile,

      FileMode.Create,

      FileAccess.Write,

      FileShare.None

      );

fsOutFile.SetLength(0);

Byte[] byteKey = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

Byte[] byteIV = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

//saAlgorithm.Mode = CipherMode.CBC;

CryptoStream cs = new CryptoStream(fsInFile,

      saAlgorithm.CreateDecryptor(byteKey,byteIV),

      CryptoStreamMode.Read);

long lFileLen = fsInFile.Length;

int iProcessed = 0;

Byte[] byteBuffer = new Byte[8];

int iRead;

do

{

      //read from the in file and write to the out file.

      iRead = cs.Read(byteBuffer,0,8);

      fsOutFile.Write(byteBuffer,0,iRead);

      iProcessed += iRead;

      Console.WriteLine("Processed {0} of {1}",iProcessed,lFileLen);

}while (iRead > 0);