Cryptography in .NET

Mark Strawmyer Presents: .NET Nuts & Bolts



The focus of this month’s article will be ill be on encrypting data using the cryptography classes available through the Microsoft .NET Framework. Providing an in depth explanation of cryptography and how it works is well beyond this column and I will leave to those more algorithm and mathematically included than I. Instead, I’ll provide an overview of cryptography, cover the basics on some of what’s available through the .NET Framework, and then we’ll focus on the use of symmetric encryption to protect data such as user passwords.

What is Cryptography?

Cryptography is used to protect data and has many valuable uses. It can protect data from being viewed, modified, or to ensure the integrity from the originator. Cryptography can be used as a mechanism to provide secure communication over an unsecured network, such as the Internet, by encrypting data, sending it across the network in the encrypted state, and then the decrypting the data on the receiving end. Encryption can also be used as an additional security mechanism to further protect data such as passwords stored in a database to prevent them from being human readable or understandable.

Encryption Components

Encryption involves the use of a cryptography algorithm combined with a key to encrypt and decrypt the data. The goal of every encryption algorithm is to make it as difficult as possible to decrypt the data without the proper key. Data is translated from its originating form into something that appears meaningless unless the proper algorithm and key are used to decrypt the data.

The Microsoft .NET Framework classes (System.Security.Cryptography) will manage the details of cryptography for you. The classes are implemented with the same interface; so working with the classes is the same across the cryptography namespace. Some of the classes in the Framework are mere wrappers for algorithms that exist in the Microsoft CrytpoAPI. Other classes are managed implementations of their respective algorithms.

Public-Key Encryption

Public-key encryption, also known as asymmetric encryption, uses a public
and private key pair to encrypt and decrypt data. The public key is made
available to anyone and is used to encrypt data to be sent to the owner of
the private key. The private key, as the name implies, is kept private.
The private key is used to decrypt the data and will only work if the
correct public key was used when encrypting the data. The private key is
the only key that will allow data encrypted with the public key to be
decrypted. The keys can be stored for use multiple times, or generated for
a one-time use.

Asymmetric encryption algorithms are usually efficient for encrypting small
amounts of data only. The following public-key algorithms are available
for use in the .NET Framework.

  • Digital Signature Algorithm (DSA)
  • RSA

Private-Key Encryption

Private-key Encryption, also known as symmetric encryption, uses a single key to encrypt and decrypt information. The key must be kept secret from those not authorized to decrypt the data lest the data be compromised. Private-key algorithms are relatively fast and can be used to encrypt and decrypt large streams of data. Private-key algorithms are known as block ciphers because they encrypt data one block at a time. A block cipher will encrypt the same input block into the same output block based on the algorithm and key. If the anything were known about the structure of the data, patterns could be detected and the key could possibly be reverse engineered. To combat this, the classes in the .NET Framework use a process known as chaining where information from the previous block is used in encrypting the current block. This helps prevent the key from being discovered. It requires an initialization vector (IV) be given to encrypt the first block of data.

The following private-key algorithms are available in the .NET Framework. Each description contains some basic information about each algorithm, including the strengths and weaknesses.

  • Data Encryption Standard (DES) algorithm encrypts and decrypts data in 64-bit blocks, using a 64-bit key. Even though the key is 64-bit, the effective key strength is only 56-bits. There are hardware devices advanced enough that they can search all possible DES keys in a reasonable amount of time. This makes the DES algorithm breakable, and the algorithm is considered somewhat obsolete.
  • RC2 is a variable key-size block cipher. The key size can vary from 8-bit up to 64-bits for the key. It was specifically designed as a more secure replacement to DES. The processing speed is two to three times faster than DES. However, the RC2CryptoServiceProvider available in the .NET Framework is limited to 8 characters, or a 64-bit key. The 8-character limitation makes it susceptible to the same brute force attack as DES.
  • TripleDES algorithm uses three successive iterations of the DES algorithm. The algorithm uses either two or three keys. Just as the DES algorithm, the key size is 64-bit per key with an effective key strength of 56-bit per key. The TripleDES algorithm was designed to fix the shortcomings of the DES algorithm, but the three iterations result in a processing speed three times slower than DES alone.
  • Rijndael algorithm, one of the Advanced Encryption Standard (AES) algorithms, was designed as a replacement for the DES algorithms. The key strength is stronger than DES, and was designed to out perform DES. The key can vary in length from 128, 192, to 256 bits in length. This is the algorithm I personally trust the most and that I’ll use for the examples contained in the column.

Hashing Algorithms

No, I’m not talking about some type of drug related activity or an extra special recipe for brownies. Hashing refers to mapping data of any length into a fixed-length byte sequence. Regardless of if the input is the contents of the library of Congress or the typing test “The quick brown fox jumps over the lazy dog” it will result in an output of the same size. Hashing also produces unique results. Just as no two snowflakes are identical, no two combinations of input will produce the same hash output. Even if the input varies by a single character it will produce different output. The .NET Framework provides support for the following hash algorithms of which I’ll leave it up to you to discover which one is right for you based on the size of the data you require.

  • HMACSHA1
  • MACTripleDES
  • MD5CryptoServiceProvider
  • SHA1Managed
  • SHA256Managed
  • SHA384Managed
  • SHA512Managed

How to Generate a Key and IV for Private-key Encryption

Each algorithm has specific key sizes that it expects for use. Each key must fit a predetermined size typically ranging from 1 character (8-bit) up to 32 characters (256-bit). Some of the algorithms support varying key sizes, but they must be within the valid ranges of key size for the particular algorithm. For our purposes, we’ll use Rijndael, which supports 128, 192, and 256 bit keys. The way to generate a 128-bit key for use is through one of the hashing algorithms. A phrase, or “secret” of any length can be hashed to generate a key of the required size for encrypting data. The following code outlines a class containing a method that takes an input phrase and generates a key and IV for use.

// Initialize internal values
this._Key = new byte[24];
this._IV = new byte[16];

// Perform a hash operation using the phrase.  This will generate
// a unique 48 character value to be used as the key and IV.
byte[] bytePhrase = Encoding.ASCII.GetBytes(Phrase);
SHA384Managed sha384 = new SHA384Managed();
sha384.ComputeHash(bytePhrase);
byte[] result = sha384.Hash;

// Transfer the first 24 characters of the hashed value to the key
// and 16 more characters for the initialization vector.
for( int loop=0; loop<24; loop++ ) this._Key[loop] = result[loop];
for( int loop=24; loop<40; loop++ ) this._IV[loop-24] = result[loop];

How to Use Private-key Encryption

The following class demonstrates how to use hashing to generate the secret key to be used for encrypting desired data. The decrypting method would simply be using the reverse functions of what is used in the Encrypt function.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

internal class MyEncryptor
{
  // Internal value of the phrase used to generate the secret key
  private string _Phrase = "";
  /// <value>Set the phrase used to generate the secret key.</value>
  public override string Phrase
  {
    set
    {
       this._Phrase = value;
       this.GenerateKey();
    }
  }

  // Internal initialization vector value to
  // encrypt/decrypt the first block
  private byte[] _IV;

  // Internal secret key value
  private byte[] _Key;

    /// <summary>
  /// Constructor
  /// </summary>
  /// <param name="SecretPhrase">Secret phrase to
       generate key</param>
    public MyEncryptor(string SecretPhrase)
  {
     this.Phrase = SecretPhrase;
  }

  /// <summary>
  /// Encrypt the given value with the Rijndael algorithm.
  /// </summary>
  /// <param name="EncryptValue">Value to encrypt</param>
  /// <returns>Encrypted value. </returns>
  public string Encrypt(string EncryptValue)
  {
    CryptoStream encryptStream = null;       // Stream used to encrypt
    RijndaelManaged rijndael = null;         // Rijndael provider
    ICryptoTransform rijndaelEncrypt = null; // Encrypting object
    MemoryStream memStream = new MemoryStream(); // Stream to contain
                                                 // data

    try
    {
      if( EncryptValue.Length > 0 )
      {
        // Create the crypto objects
        rijndael = new RijndaelManaged();
        rijndael.Key = this._Key;
        rijndael.IV = this._IV;
        rijndaelEncrypt = rijndael.CreateEncryptor();
        encryptStream = new CryptoStream(
             memStream, rijndaelEncrypt, CryptoStreamMode.Write);

        // Write the encrypted value into memory
        byte[] input = Encoding.UTF8.GetBytes(EncryptValue);
        encryptStream.Write(input, 0, input.Length);
        encryptStream.FlushFinalBlock();

        // Retrieve the encrypted value and return it
        return( Convert.ToBase64String(memStream.ToArray()) );
      }
      else
      {
        return "";
      }
    }
    finally
    {
      if( rijndael != null ) rijndael.Clear();
      if( rijndaelEncrypt != null ) rijndaelEncrypt.Dispose();
      if( memStream != null ) memStream.Close();
    }
  }

  /*****************************************************************
   * Generate an encryption key based on the given phrase.  The
   * phrase is hashed to create a unique 32 character (256-bit)
   * value, of which 24 characters (192 bit) are used for the
   * key and the remaining 8 are used for the initialization
   * vector (IV).
   *
   * Parameters:  SecretPhrase - phrase to generate the key and
   * IV from.
   *
   * Return Val:  None
   ***************************************************************/
  private void GenerateKey(string SecretPhrase)
  {
    // Initialize internal values
    this._Key = new byte[24];
    this._IV = new byte[16];

    // Perform a hash operation using the phrase.  This will
    // generate a unique 32 character value to be used as the key.
    byte[] bytePhrase = Encoding.ASCII.GetBytes(SecretPhrase);
    SHA384Managed sha384 = new SHA384Managed();
    sha384.ComputeHash(bytePhrase);
    byte[] result = sha384.Hash;

    // Transfer the first 24 characters of the hashed value to the key
    // and the remaining 8 characters to the intialization vector.
    for( int loop=0; loop<24; loop++ ) this._Key[loop] = result[loop];
    for( int loop=24; loop<40; loop++ ) this._IV[loop-24] = result[loop];
  }
}

Using Encryption to Protect Sensitive Data Stored in a Database

This brings us to a possible and often overlooked use for encryption, which is encrypting data such as passwords that are stored in a database. There are several benefits to storing sensitive data such as passwords in encrypted format.

  • It keeps sensitive application data such as passwords secret from those authorized to view data that ideally should not be able to see application specific data such as passwords.
  • It keeps passwords protected from unauthorized access. If the database is somehow compromised, the intruder must now the correct algorithm and key to decrypt the sensitive data.
  • Passing direct input to a query is dangerous because you never know what the input may contain. Suppose a stored procedure including something like "WHERE vc_Login = @Login and Password = @Password" is used for authentication. A user name of "'m' or 1=1" with the same for the password could evaluate to a true statement and result in incorrectly allowing access. If the user id and or password were stored encrypted then the data would not evaluate to a valid SQL statement because an encrypted form of the statement would be used in the evaluation.

Possible Enhancements

Now we have a simple class that will allow us to encrypt data using the Rijndael algorithm. There are enhancements you could make to this class to make it more useful. Here are some ideas to consider for yourself.

  • Complete the decryption functionality in the MyEncryptor class.
  • We explored one possible use of encryption for protecting data in a database. There are many other uses to explore.
  • Experiment with the other encryption algorithms to determine the one that is right for you and get to know the strengths and weaknesses.
  • Decide how you want to handle storage of the secret phrase so that your application will be able to successfully encrypt and decrypt data consistently. You could hard code the key into the application, or derive it using some other mechanism.
  • Create a manager object similar to what we did in the prior article on "Database Independent Data Access".
    • A manager object could be used to have a single interface for encryption and decryption in your applications.
    • The manager object could allow for configuration settings dictating which encryption algorithm to use when calling the Encrypt or Decrypt methods.
    • Enhance the GenerateKey method to create a variable size key and IV based on the desired algorithm.

Future Columns

I don't have a specific topic yet identified for the next column. I have had several great email discussions with folks that may yield a topic of the next article. If you have something in particular that you would like to see explained please email me at mstrawmyer@crowechizek.com

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.

# # #

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read