The days where developers have to fiddle about with complex APIs and byte lengths of plain text are now over, at least for the .NET developer. Previously, all documentation for the Crypto API was in C, and VB developers who may well have needed to use the Crypto API had to struggle working out API declarations for use within their VB/COM applications.
The .NET framework provides a comprehensive library of cryptographic classes which can be used for anything from hashing passwords, encrypting file contents, to implementing symmetric key exchange sessions over the internet. In the following two articles we'll study the use of these cryptographic objects and simplify some crypto terminology.
Hashing Functions
The following program has two classes: one to hash passwords and the other to encrypt files or streams. Message digests are implemented in .NET in two forms: MD5 and SHA1. Both are equally useful, MD5 returns 16 bytes based on any input content and this is unique for that content. SHA1 returns 20 bytes. These digests can be used as follows:
- Verifying the contents of a file being transferred by computing the digest based on the file contents - in this way if the file contents have changed there will be a discrepancy which will be apparent in the digest calculated previously to the current digest.
- Using a username/password to provide a secure login mechanism. The user would type these two into a login prompt and the concatenation of the two words would be hashed and compared to a stored hash value to determine authentication.
Hashing is especially useful for controlling access to resources on a website. As such ASP.NET can be used in conjunction with the C# objects below to create a user repository in a database. First the user enters a username and a password. These two strings are then concatenated and the result will be passed to an MD5 or SHA1 object. A unique hash would be computed and this hash could be compared to one that was stored earlier in the database when the user account was created.
The simplicity of this scheme is that the user is the only one who needs to know the password.
A malicious person viewing the contents of the database will see the username with the password stored next to the username as a hash - they will never know more than 50% of what is required to use the account. In effect all details can be hashed including the credit card number with the username for secondary access if someone has forgotten their password.
After a successful database hash is matched with one computed on the fly then a token can be given which allows access to certain resources on the website. In effect this is similar to how Microsoft has implemented hashing from within the web.config page (which is one of the two key pages now to any ASP.NET site along with the global.asax).
This is a section of a web.config page:
<configuration>
<security>
<authentication mode="Cookie">
<cookie cookie="token" loginurl="login.aspx" decryptionkey="autogenerate">
<credentials passwordformat="SHA1">
<user name="newuser" password="UEHRYF83478FHKS6RT45" />
</credentials>
</cookie>
</authentication>
<authorization>
<deny users="?" />
</authorization>
</security>
</configuration>
The section that interests us at the moment is the line:
<credentials passwordformat="SHA1">
This means that all passwords will have SHA1 hashes computed and stored for them. The token discussed in the previous paragraphs is given to the web user in the form of a cookie. We won't be discussing this here but the CookieAuthentication object has static methods that allow it to control the login process.
The hashing object wrappers below can be used to gain greater clarity in the hashing process and possibly to replace use of the web.config to do the same. You can, as you will see below, hash any number of things (including file contents); hashing multiple strings can be done by just using string concatenation followed by computation of the hash itself.
Encryption
The second class is used to generate symmetric keys. The algorithms used are DES, Triple DES and RC2 - these keys can be used to encrypt any volume of data. A key is generated by any of the 3 above algorithms and is then used to encrypt some plaintext. The result is called cipher text. Symmetric encryption can be used for the following:
- Encrypt the contents of a file
- Encrypt the contents of stream (i.e. a data stream being transmitted over the internet).
The above key algorithms are just used to generate the key itself. The encryption is done by XORing the key bits to the bits of the plaintext. XOR is used as the key can be XORed again to the cipher text to return to the original plaintext. Cipher block chaining is used to add the bits of the previous cipher text to the plaintext following. In this way there will be no repetition in the cipher text and the key won't be able to be derived by looking at the similarities within the cipher text itself.

From the diagram you can see that the plaintext is encrypted in the first block (denoted by gray) and the resulting cipher text is Xored with the plaintext from the next block (denoted by yellow) before encryption.
Xor is a binary operation that can be done on a bit. A Xor truth table would look like the following:
| Input bits | Result |
| 00 | 0 |
| 10 | 1 |
| 01 | 1 |
| 11 | 0 |
Unlike a conventional Or operation it produces true when either input bit is 1 but not both. As all text characters can be represented in ASCII (an 8 bit notation) it is the bits in the ASCII representation of the character that are XORed with the bits of the key to provide the results given in the truth table above. As you can see above, if a one bit key (1) encrypts a one bit plaintext 1 the result would be 0. To decrypt this all you would need to do is the XOR operation again so 0 Xor 1 would be 1 thus arriving at the original plaintext.
To begin the project a reference must be set in the project pane to the System.Security.Cryptography namespace.
Then to import the Crypto library in the System.Security.Cryptography namespace to avoid prefixing the cryptographic objects:
using System.Security.Cryptography;
This namespace has a wealth of uses. Some we will cover in this article and others we'll save for the future. It contains implementations of RSA which will be used in part II to create a key-exchange program. Most of you will be familiar with the internet use of SSL. Well, effectively SSL uses Private/Public key cryptography, i.e. a digital certificate (issued by a certified authority) to allow a web browser to generate a symmetric key and send data securely to the web server (so that only the browser and server know the key). This whole processor is called key-exchange and objects exist in the Cryptography namespace to allow for the formatting of these keys and implementations of the key-exchange.
Some support classes exist to allow for the generation and maintenance of keys also, including classes which allow changeable bit padding for encryption and other classes that allow one to customize encryption for the task at hand.
We will also have to reference the namespace System.Text specifically because we will need to use the Encoding objects static methods to convert between strings and byte arrays. We will also refer to System.IO since we will be reading and writing to files.
using System.Text;
using System.IO;
These base classes can now be accessed without the need for the namespace prefix:
File.Exists("temp.txt");
Or
FileStream f = FileStream("temp.txt", FileMode.Create, FileAccess.Write);
These two classes are found within the System.IO namespace and are invaluable for reading and writing byte array to files. As such they are used throughout the code to read from plaintext files and to write out to cipher text files.
Hashing Class
public class HashLoginCredentials
{
public enum HashTypeDef { MD5, SHA1 }
bool isSha = false;
string[] gCredentials = {};
string plain = String.Empty;
byte[] hashed = {};
byte[] bHashResult = new byte[21];
public HashLoginCredentials(HashTypeDef htd, params string[] sCredentials)
{
if(htd==HashTypeDef.SHA1)
{
isSha = true;
}
gCredentials = sCredentials;
}
public string Hash(bool isBase64)
{
if(isSha)
{
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
for(int i = 0; i<gCredentials.Length; i++) plain += gCredentials[i];
byte[] bSha1Hash = Encoding.ASCII.GetBytes(plain);
//sha1.TransformBlock(bSha1Hash, 0, bSha1Hash.Length, bHashResult, 0);
bHashResult = sha1.ComputeHash(bSha1Hash);
hashed = bHashResult;
}
else
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
for(int i = 0; i<gCredentials.Length; i++) plain += gCredentials[i];
byte[] bMD5Hash = Encoding.ASCII.GetBytes(plain);
//md5.TransformBlock(bMD5Hash, 0, bMD5Hash.Length, bHashResult, 0);
bHashResult = md5.ComputeHash(bMD5Hash);
hashed = bHashResult;
}
if(isBase64)
{
return Convert.ToBase64String(hashed);
}
else
{
return Encoding.ASCII.GetString(hashed);
}
}
}
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:
internal class GenerateDesKey : SymDefKey
{
private byte[] desIVVal = {
10, 20, 30, 40, 50, 60, 70, 80
};
byte[] desKeyVal = {};
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.
However, to use this, the high encryption pack needs to be installed on Windows. This can be found on the Microsoft website
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.
ReadKeyFileContents(ref keyVal, ref IVVal, outName);
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.
Using ICryptoTransform Interfaces and Cryptographic Objects
In the following code we will see how the actual encryption is done. Formerly (in beta 1) we would have defined a class which implemented ICryptoStream to allow us to write to any stream - this could then be custom defined. The authors of the .NET Crypto libraries have simplified this even further in Beta 2 by allowing ICryptoTransform interfaces to be created that represent either encryption or decryption. This contains all the key and IV information.
try
{
//Here we create the decryption objects but if the key file doesn't
//exist, automatically throw an exception again
outName = fileNameOrTextToEncrypt.Substring(0, fileNameOrTextToEncrypt.Length - 3)+"key";
ReadKeyFileContents(ref keyVal, ref IVVal, outName);
ICryptoTransform streamDecrypt = d.CreateDecryptor(keyVal, IVVal);
CryptoStream csDecrypt = new CryptoStream(fOut, streamDecrypt, CryptoStreamMode.Write);
len = 0;
lenCount = 0;
byte[] bOutputDecrypt = new byte[4096];
while (lenCount < fileLength) {
len = fIn.Read(bContents,0,4096);
csDecrypt.Write(bContents,0,len);
lenCount = lenCount + len;
}
csDecrypt.FlushFinalBlock();
csDecrypt.Close();
}
catch(Exception e)
{
throw e;
}
Some of the above file handling code might be familiar to .NET aficionados. The CryptoStream object is created and takes as its arguments the ICryptoTransform interface (which is our Decryptor and contains the key and IV that we need). It also needs a valid stream object. In the above code it is a FileStream although this is because we are going to write to a file.
In a later piece of code we use a MemoryStream in the same place to write to memory and retrieve our encrypted output as a byte array.
However, it just needs to be a stream which means that it can be written to any networking stream object too. In the while loop we read from one file and then write to the CryptoStream (effectively in this case another file). The Stream can be written to any number of times, then FlushFinalBlock() is called followed by Close(), which will flush the stream buffer. Close releases all handles (in this case to files).
MemoryStream mStream = new MemoryStream();
mStream.Position = 0;
if(!bToBeEncrypted)
{
ICryptoTransform streamDecrypt = d.CreateDecryptor(keyVal, IVVal);
CryptoStream csEncryptString = new CryptoStream(mStream, streamDecrypt, CryptoStreamMode.Write);
csEncryptString.Write(Encoding.Default.GetBytes(fileNameOrTextToEncrypt), 0,
Encoding.Default.GetBytes(fileNameOrTextToEncrypt).Length);
csEncryptString.FlushFinalBlock();
csEncryptString.Close();
}
As can be seen from the above implementation a MemoryStream can be used in place of a FileStream. This exemplifies the idea that any stream can be used. A MemoryStream is useful in this effect as an area of memory can be written to as a stream and later discarded when the contents are retrieved into an array or string.
private void CreateKeyFile(File f)
{
string outNameKey = f.FullName.Substring(0, f.FullName.Length - 3)+"key";
FileStream fKey = new FileStream(outNameKey, FileMode.Create, FileAccess.Write);
StreamWriter sKey = new StreamWriter(fKey);
sKey.WriteLine(Convert.ToBase64String(keyAlg.Key()));
sKey.WriteLine(Convert.ToBase64String(keyAlg.IV()));
sKey.Flush();
sKey.Close();
fKey.Close();
}
After the first encryption is done then the key file is written in the same directory as the plaintext and the cipher text output. The above method is used to write a Base64 encoded version of the key and the IV to a file. This can again be extended to use a key file which contains multiple keys.
I've written this article into a .dll project which can be used generically within a project. The following sample code (ConsoleClient.cs) explains how to use the objects contained therein:
try
{
//First file encryption or decryption
CryptoWrapper.EncryptStringOrFile e = new EncryptStringOrFile(EncryptStringOrFile.KeyEncryptAlgorithm.DES,
"C:\temp.enc");
string temp = e.Encrypt();
//Then hash passwords ans usernames
CryptoWrapper.HashLoginCredentials h = new HashLoginCredentials(HashLoginCredentials.HashTypeDef.SHA1,
"username", "password");
string hash = h.Hash(true);
//Then encrypt a stream
CryptoWrapper.EncryptStringOrFile e1 = new EncryptStringOrFile(EncryptStringOrFile.KeyEncryptAlgorithm.DES,
"EncryptOrDecryptThisText");
string temp1 = e1.Encrypt();
Console.WriteLine("filename is: "+temp);
Console.WriteLine("hash is: "+hash);
Console.WriteLine("Encrypted text is: "+temp1);
Console.WriteLine("\r\nPress any key to exit...");
Console.Read();
return 0;
}
catch(Exception e)
{
Console.WriteLine(e.StackTrace);
return 0;
}
Here is an example of the encrypted output from the above main() method:

In the first line "temp.enc" is passed to the EncryptFileOrString object and then the encrypt method is called which looks for the key file in the respective directory and decrypts the file into "temp.tmp ". It returns the name of the output file is successful.
In the second operation a HashLoginCredentials object is created and it is instructed to use a SHA1 hash, username and password is passed into this constructor and the resultant hash is returned.
In the final operation a DES algorithm is used to encrypt some plaintext. The cipher text is returned to the console in the form of a string. The only prerequisite is for a file called C:\temp.key to exist so that key information can used to encrypt and decrypt plain and cipher text respectively.
A test harness for the .dll has been provided in ASP.NET. After extracting the .zip file, typing http://localhost/CryptoWrapper/login.aspx (assuming you have installed a web server) should present the following screen:

There is a readme.txt file to accompany the test harness which should be read prior to using the page. However, code from the test harness can be easily adapted into an application to allow, amongst other things, logins and password hashing from which can be stored and retrieved from an XML file and also applications that allow files to be uploaded but encrypt them on the server to allow a greater level of security.
Conclusion
We have see how to store and retrieve encrypted data from a file system using the examples of an encrypted file in the C:\Temp directory and encrypted passwords. In the next article in this series, we will look at encryption of data over a network.
By Wrox Team
Source: devarticles.com
