Using .NET Cryptography

Cryptography in the .NET Framework breaks down to the following areas:

  • Hashing—This involves creating a byte array that uniquely defines a set of data. For example, you can create a hash of a file. Then in the future, if you suspect that the file has changed, you can simply compare the original hash with a new hash to see if the file has been altered.

  • Symmetric encryption—This involves a set of data that is encrypted with a key or password and decrypted with the same key or password. This is the traditional form of encryption in which a single key unlocks the data.

  • Asymmetric encryption—Asymmetric encryption involves two keys: a public key and a private key. What is encrypted with one key can only be decrypted with the other. Typically, a server encrypts data with a private key and a client decrypts the data with a public key. The public key can be distributed because it only holds the key to decrypting the data. Asymmetric encryption is much slower than symmetric encryption; therefore, it is typically only used to pass encrypted keys for symmetric encryption.

The .NET Framework has classes that support all of these aspects of cryptography. These classes are mostly wrappers around the CryptAPI method calls that make using cryptography much easier.

Three applications have been developed to show how to use .NET cryptography to hash a set of data and how to encrypt a set of data. The first application hashes two user-supplied strings and compares the hash values to see if the strings are equivalent. Figure 16.17 shows what the application looks like.

Figure 16.17. Hashing two strings.


The complete source for this application is in the CryptographyHash directory.

Listing 16.18 shows how the hash is computed for the strings.

Listing 16.18. Computing a Hash
private void OnComputeHash(object sender, System.EventArgs e)
{
    // Convert A to byte array
    Byte[] data1ToHash = ConvertStringToByteArray(adata.Text);
    // Convert B to byte array
    Byte[] data2ToHash = ConvertStringToByteArray(bdata.Text);

    // Create hash value from String 1 using MD5
    // instance returned by Crypto Config system
    byte[] hashvaluea = ((HashAlgorithm) CryptoConfig.CreateFromName("MD5")).ComputeHash
(data1ToHash);

    ahash.Text = BitConverter.ToString(hashvaluea);

    // Create hash value of String 2 using directly
    // created instance of the MD5 class
    byte[] hashvalueb = (new MD5CryptoServiceProvider()).ComputeHash(data2ToHash);
    bhash.Text = BitConverter.ToString(hashvalueb);

    // Memberwise compare of hash value bytes
    bool same = true;
    if(hashvaluea.Length == hashvalueb.Length)
    {
        int i = 0;
        do
        {
            if(hashvaluea[i] != hashvalueb[i])
            {
                same = false;
                break;
            }
            i++;
        }
        while(i < hashvaluea.Length);
    }
    else
        same = false;
    if(same)
        comparison.Text = "Same";
    else
        comparison.Text = "Different";
}

First, each string needs to be converted to a byte array using the static function ConvertStringToByteArray. Next, the hash is computed for the byte array. Two operations are happening here: the algorithm is retrieved by a name that will be used to hash the byte array, and the hash is computed using ComputeHash. The hash is converted to a displayable string using Bitconverter.ToString. Then the two hash values are compared. If the hash values differ in length, they are different. If they are the same length, then each byte of the hashed string must be the same for the strings to be the same. This is not practical for strings that a user can type, but if a byte array could be formed representing the content of a file, then clearly comparing the hash values is preferable to a byte-by-byte comparison of the file's contents.

The next application uses hashing in a more practical, real-world sample. This application is a read-only messenger application. The complete source is in the Messenger directory. This application implements a portion of the RFC for MSN Messenger (which is included in the application directory). The application only signs on to the Messenger services and listens for messages sent to it. The application as is does not allow for responding or initiating a conversation. You can read the RFC and enhance this application to include more functionality as needed. This application was primarily developed to show a possible application for hashing. Listing 16.19 shows a portion of the authentication code that is used for Messenger authentication.

Listing 16.19. Encrypting a File
// Request Security Package

. . .
// Send Initial Authentication Userid

. . .

// Parse MD5 Hash Check Response
// Format: USR 5 MD5 S 989048851.1851137130

. . .
// str3 = MD5 Hash Check
str3 = str3.Trim();
str2 = str3 + _password;

// Obtain MD5 Hash

MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] md5hash = md5.ComputeHash(Encoding.UTF8.GetBytes(str2));
str1 = GetByteString(md5hash);

tid = Convert.ToString(MessengerForm.GetNextTrialID());
str2 = "USR " + tid + " MD5 S " + str1;
SendNSData(str2);
str1 = ReceiveNSData();

The protocol for getting authorization from a Messenger server is as follows:

1.
Request the security package and send the user handle (e-mail address).

2.
Append the password to the hash value that was sent back.

3.
Hash the concatenated string.

4.
Send the hash of the concatenated string back to the server for verification and authorization.

By forming a hash, you don't have to send the password or an encrypted form of the password across the wire. Because the hash is formed from the string passed to you plus the password, the value sent will be different every time.

Note

Occasionally while debugging this messenger application, the messenger server might become “confused.” This can be due to timing problems with setting breakpoints at certain parts of the code and because of improper or no response to some of the requests that the server sends. For five minutes or so, you might not be able to log on using this application or MSN Messenger. Be patient. Eventually the server will right itself and allow you to log in again. If this is intolerable, then you might just want to read the code and the RFC and recognize that .NET Cryptography can easily be used to provide a hashing function. For the more adventuresome, you will undoubtedly want to enhance this code to add functionality and make it more robust.


Next, an application that encrypts a file is built.

Figure 16.18. Encrypting a file.


Listing 16.20 shows that a file can be encrypted with the default encryption algorithm.

Listing 16.20. Encrypting a File
private void OnEncrypt(object sender, System.EventArgs e)
{
    // Create the file streams to handle the input and output files.
    FileStream fin = new FileStream(filePath.Text, FileMode.Open, FileAccess.Read);
    FileStream fout = new FileStream(encryptedPath.Text, FileMode.OpenOrCreate, FileAccess
.Write);
    fout.SetLength(0);

    // Create variables to help with read and write.
    // This is intermediate storage for the encryption.
    byte[] bin = new byte[fileChunkSize];
    // This is the total number of bytes written.
    long rdlen = 0;
    // This is the total length of the input file.
    long totlen = fin.Length;
    // This is the number of bytes to be written at a time.
    int len;

    Debug.WriteLine("Encrypting...");
    DateTime start = DateTime.Now;

    // Creates the default implementation
    //DES instance with random key
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    fileKey = des.Key;
    fileIV = des.IV;

    //Create DES Encryptor from this instance
    ICryptoTransform desencrypt = des.CreateEncryptor();

    //Create Crypto Stream that transforms file stream using des encryption
    CryptoStream encStream = new CryptoStream(fout,desencrypt,CryptoStreamMode.Write);

    //Read from the input file; then encrypt and write to the output file.
    while(rdlen < totlen)
    {
        len = fin.Read(bin, 0, fileChunkSize);
        encStream.Write(bin, 0, len);
        rdlen = rdlen + len;
    }
    fileSize.Text = Convert.ToString(rdlen);
    encStream.Close();
    fout.Close();
    fin.Close();
    DateTime stop = DateTime.Now;
    TimeSpan elapsed = stop - start;
    encryptTime.Text = string.Format("{0:F} ", (double)elapsed.TotalMilliseconds / 1000.0);
    decryptButton.Enabled = true;
}

The first portion of this code that deals with cryptography is the line that creates a DESCryptoServiceProvider. Only one constructor exists; therefore, to modify the default behavior, you need to use the properties of the class to modify the specific instance that is created. Notice that the key and the initialization vector are stored so that they can be used to decrypt the file. An encryptor interface is created and an encryption stream is associated with the created encryptor. After the encryption stream is created, the decrypted file can be processed much like any other stream in the system. In this sample, the stream is simply written out to disk and the elapsed time for decryption is recorded.

Listing 16.21 shows that a file can be decrypted with the default encryption algorithm using the same key and initialization vector that were used during the encryption process.

Listing 16.21. Decrypting a File
private void OnDecrypt(object sender, System.EventArgs e)
{
    // Create the file streams to handle the input and output files.
    FileStream fin = new FileStream(encryptedPath.Text, FileMode.Open, FileAccess.Read);
    FileStream fout = new FileStream(filePath.Text + ".decrypted", FileMode.OpenOrCreate,
 FileAccess.Write);

    // Create variables to help with read and write.
    // This is intermediate storage for the encryption.
    byte[] bin = new byte[fileChunkSize];
    // This is the total number of bytes written.
    long rdlen = 0;
    // This is the total length of the input file.
    long totlen = fin.Length;
    // This is the number of bytes to be written at a time.
    int len;

    DateTime start = DateTime.Now;
    Debug.WriteLine("Decrypting...");

    // Creates the default implementation, which is RijndaelManaged.
    //DES instance with random key
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    //create DES Encryptor from this instance
    ICryptoTransform desdecrypt = des.CreateDecryptor(fileKey, fileIV);

    //Create Crypto Stream that transforms file stream using des encryption
    CryptoStream decStream = new CryptoStream(fout, desdecrypt, CryptoStreamMode.Write);

    //Read from the input file; then encrypt and write to the output file.
    while(rdlen < totlen)
    {
        len = fin.Read(bin, 0, fileChunkSize);
        decStream.Write(bin, 0, len);
        rdlen = rdlen + len;
    }
    encryptedSize.Text = Convert.ToString(rdlen);
    decStream.Close();
    fout.Close();
    fin.Close();
    DateTime stop = DateTime.Now;
    TimeSpan elapsed = stop - start;
    decryptTime.Text = string.Format("{0:F} ", (double)elapsed.TotalMilliseconds / 1000.0);
}

This code is almost identical to Listing 16.20 with the exception that now a decryptor is created using the cached key and initialization vector. Like Listing 16.20, a stream is created and the decrypted file is written out to the decrypted file. You can manually compare (using windiff, for example) the output of the decryption process with the original file to prove to yourself that the encryption/decryption process does not change the file content.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.66.178