In this article, Richard takes us through some of the cryptography tools available in the .NET Framework that provide hashing and encryption of different types of data.
This is a very simple wrapper class for hashing but immensely useful. As mentioned before any text can have a unique message digest. The two implementations in .NET used here are MD5 and SHA1 which are the most common. If we take a more detailed look at the cryptographic hashing objects themselves we begin with the SHA1CryptoServiceProvider object, we can use the ComputeHash(byte[]) method to put any amount of data into the hashing object. This method returns the value of the hash in the form of a byte array. In this implementation a string is returned from this hash wrapper object instead of a byte array, simply because this can be written to a database table very easily for a particular user or in an XML file as I've done in the ASP.NET implementation of this object.
Convert.ToBase64String(hashed);
The above line of code is very useful. A static method of the Convert object enables us to pass a byte array and retrieve a Base64 encoded string. Base64 defines a character set of upper and lower case alphabet characters, digits 0-9 and a plus sign, a slash and a space. This means that if any piece of encrypted text or hashed text is converted into this from ASCII text it will always be viewable and printable. A Base64 string always has an equals sign at the end (to show that it is Base64). See the code below:
string temp_test = String.Copy("ABCDE&^%$!@");
...which produces as a Base64 output:
QUJDREUmXiUkIUA=
Using the encoding object is also very useful. It can be used to convert characters to ASCII from Unicode or vice-versa. It can be used to get the default system encoding and translate between strings and byte arrays or char arrays etc.
Encoding.ASCII.GetString(hashed);
Encryption Classes
There are three internal classes which map onto the 3 key algorithms discussed above; one of them looks as follows:
public GenerateDesKey() { DESCryptoServiceProvider desKey = new DESCryptoServiceProvider(); desKey.GenerateKey(); desKeyVal = desKey.Key; desKey = null; } public byte[] Key() { //Returns key value return desKeyVal; } public byte[] IV() { //Returns IV Value return desIVVal; } }
The value IV is used to store an Initialization Vector, which is used to make it a little more complicated to break the encryption. The IV is added to the plaintext block to be encrypted using CBC making it slightly harder to crack.
This is a simple class which implements a common interface: SymDefKey so that Key() and IV() methods can be defined on all these wrapper classes. The DESCryptoServiceProvider is used here to create a DES key (DES keys are normally 56 bits long). As can be seen the GenerateKey() method is used to generate a key within the DESCryptoServiceProvider object and it can be retrieved through the Key property. IV's are only really used for DES now since key lengths are small and DES keys have been found via brute force and decryptions have followed. With Triple DES (DES3) we have 168 bit keys (or three 56 bit keys) which are used to encrypt, decrypt and re-encrypt the plaintext - Essentially this is an extension of DES using the same key sizes but to perform operations on the plaintext 3 times. This is the most common key used and is far more secure than DES.
case KeyEncryptAlgorithm.DES: keyAlg = (SymDefKey) new GenerateDesKey(); keyTemp = KeyEncryptAlgorithm.DES; break; //Create an instance of internal DES3key class case KeyEncryptAlgorithm.DES3: keyAlg = (SymDefKey) new GenerateDes3Key(); keyTemp = KeyEncryptAlgorithm.DES3; break; //Create an instance of internal RC2 class case KeyEncryptAlgorithm.RC2: keyAlg = (SymDefKey) new GenerateRC2Key(); keyTemp = KeyEncryptAlgorithm.RC2; break;
The above segment of code shows that the class defined above casts to the interface type that it implements (SymDefKey) - all of these wrapper classes implement the methods Key and IV which enable the objects which we'll talk through later on to retrieve and use to get do the actual work (encryption or decryption). This code exists in the object constructor.
A boolean flag is set to enable the object to work out whether the user has passed a file name (which must exist) or a string - It will check first if the file exists, if not then it will assume that the user has passed a string needs to be encrypted. This is set after the file name is tested for existence.
If the file does exist then the following code is executed:
if(!fileNameOrTextToEncrypt.EndsWith(".enc")) { //Calculate output name of encryption file outName = fileNameOrTextToEncrypt.Substring(0, fileNameOrTextToEncrypt.Length - 3)+"enc"; } else { //Calculate output name of decrypted file outName = fileNameOrTextToEncrypt.Substring(0, fileNameOrTextToEncrypt.Length - 3)+"tmp"; bToBeEncrypted = false; }
This forms part of the Encrypt() method. It checks to see if the filename being passed ends with .enc. If so then the method will create an output file of the same name in the same directory ending with .tmp which will contain the decrypted output from the .enc file.
Assuming the latter, then the program will be unable to proceed and will generate an exception if it is unable to find a .key file in the current directory containing a key and an IV value. This ReadFileContents() method resides within a try-catch block ensuring that the file will have to be present for decryption.