DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 14.2 Encrypting/Decrypting a String

Problem

You have a string you want to be able to encrypt and decrypt—perhaps a password or software key—which will be stored in some form accessible by users, such as in a file, the registry, or even a field, that may be open to attack from malicious code.

Solution

Encrypting the string will prevent users from being able to read and decipher the information. The following class, CryptoString, contains two static methods to encrypt and decrypt a string and two static properties to retrieve the generated key and inititialization vector (IV—a random number used as a starting point to encrypt data) after encryption has occurred:

using System;
using System.Security.Cryptography;

public sealed class CryptoString
{
    private CryptoString( ) {}

    private static byte[] savedKey = null;
    private static byte[] savedIV = null;

     public static byte[] Key
    {
        get { return savedKey; }
        set { savedKey = value; }
    }

     public static byte[] IV
    {
        get { return savedIV; }
        set { savedIV = value; }
    }

    private static void RdGenerateSecretKey(RijndaelManaged rdProvider)
    {
        if (savedKey == null)
        {
            rdProvider.KeySize = 256;
            rdProvider.GenerateKey( );
            savedKey = rdProvider.Key;
        }
    }

    private static void RdGenerateSecretInitVector(RijndaelManaged rdProvider)
    {
        if (savedIV == null)
        {
            rdProvider.GenerateIV( );
            savedIV = rdProvider.IV;
        }
    }

    public static string Encrypt(string originalStr)
    {
        // Encode data string to be stored in memory
        byte[] originalStrAsBytes = Encoding.ASCII.GetBytes(originalStr);
        byte[] originalBytes = {};

        // Create MemoryStream to contain output
        MemoryStream memStream = new MemoryStream(originalStrAsBytes.Length);

        RijndaelManaged rijndael = new RijndaelManaged( );

        // Generate and save secret key and init vector
        RdGenerateSecretKey(rijndael);
        RdGenerateSecretInitVector(rijndael);

        if (savedKey == null || savedIV == null)
        {
            throw (new NullReferenceException(
                    "savedKey and savedIV must be non-null."));
        }

        // Create encryptor, and stream objects
        ICryptoTransform rdTransform = rijndael.CreateEncryptor((byte[])savedKey.
                            Clone( ),(byte[])savedIV.Clone( ));
        CryptoStream cryptoStream = new CryptoStream(memStream, rdTransform, 
                            CryptoStreamMode.Write);

        // Write encrypted data to the MemoryStream
        cryptoStream.Write(originalStrAsBytes, 0, originalStrAsBytes.Length);
        cryptoStream.FlushFinalBlock( );
        originalBytes = memStream.ToArray( );

        // Release all resources
        memStream.Close( );
        cryptoStream.Close( );
        rdTransform.Dispose( );
        rijndael.Clear( );

        // Convert encrypted string
        string encryptedStr = Convert.ToBase64String(originalBytes);
        return (encryptedStr);
    }

    public static string Decrypt(string encryptedStr)
    {
        // Unconvert encrypted string
        byte[] encryptedStrAsBytes = Convert.FromBase64String(encryptedStr);
        byte[] initialText = new Byte[encryptedStrAsBytes.Length];

        RijndaelManaged rijndael = new RijndaelManaged( );
        MemoryStream memStream = new MemoryStream(encryptedStrAsBytes);

        if (savedKey == null || savedIV == null)
        {
            throw (new NullReferenceException(
                    "savedKey and savedIV must be non-null."));
        }

        // Create decryptor, and stream objects
        ICryptoTransform rdTransform = rijndael.CreateDecryptor((byte[])savedKey.
                            Clone( ),(byte[])savedIV.Clone( ));
        CryptoStream cryptoStream = new CryptoStream(memStream, rdTransform, 
                            CryptoStreamMode.Read);

        // Read in decrypted string as a byte[]
        cryptoStream.Read(initialText, 0, initialText.Length);

        // Release all resources
        memStream.Close( );
        cryptoStream.Close( );
        rdTransform.Dispose( );
        rijndael.Clear( );

        // Convert byte[] to string
        string decryptedStr = Encoding.ASCII.GetString(initialText);
        return (decryptedStr);
    }
}

Discussion

The CryptoString class follows a singleton design pattern. This class contains only static members, except for the private instance constructor, which prevents anyone from directly creating an object from this class.

This class uses the Rijndael algorithm to encrypt and decrypt a string. This algorithm is found in the System.Security.Cryptography.RijndaelManaged class. This algorithm requires a secret key and an initialization vector; both are byte arrays. A random secret key can be generated for you by calling the GenerateKey method on the RijndaelManaged class. This method accepts no parameters and returns void. The generated key is placed in the Key property of the RijndaelManaged class. The GenerateIV method generates a random initialization vector and places this vector in the IV property of the RijndaelManaged class.

The byte array values in the Key and IV properties must be stored for later use and not modified. This is due to the nature of private-key encryption classes, such as RijndaelManaged. The Key and IV values must be used by both the encryption and decryption routines to successfully encrypt and decrypt data.

The SavedKey and SavedIV private static fields contain the secret key and initialization vector, respectively. The secret key is used by the encryption and decryption methods to encrypt and decrypt data. This key must be used by both the encryption and decryption methods in order to successfully encrypt and then decrypt the data. This is why there are public properties for these values, so they can be stored somewhere secure for later use. This means that any strings encrypted by this object must be decrypted by this object. The initialization vector is used to prevent anyone from attempting to decipher the secret key.

There are two methods in the CryptoString class, RdGenerateSecretKey and RdGenerateSecretInitVector, that are used to generate a secret key and initialization vector, when none exist. The RdGenerateSecretKey method generates the secret key, which is placed in the SavedKey field. Likewise, the RdGenerateSecretInitVector generates the initialization vector, which is placed in the SavedIV field. There is only one key and one IV generated for this class. This enables the encryption and decryption routines to have access to the same key and IV information at all times.

The Encrypt and Decrypt methods of the CryptoString class do the actual work of encrypting and decrypting a string, respectively. The Encrypt method accepts a string that you want to encrypt and returns an encrypted string. The following code calls this method and passes in a string to be encrypted:

string encryptedString = CryptoString.Encrypt("MyPassword");
Console.WriteLine("encryptedString: " + encryptedString);
// get the key and IV used so you can decrypt it later
byte [] key = CryptoString.Key;
byte [] IV = CryptoString.IV;

Once the string is encrypted, the key and IV are stored for later decryption. This method displays:

encryptedString: Ah4vkmVKpwMYRT97Q8cVgQ==

The following code sets the key and IV used to encrypt the string, then calls the Decrypt method to decrypt the previously encrypted string:

CryptoString.Key = key;
CryptoString.IV = IV;
string decryptedString = CryptoString.Decrypt(encryptedString);
Console.WriteLine("decryptedString: " + decryptedString);

This method displays:

decryptedString: MyPassword

There does not seem to be any problems with using escape sequences such as \r, \n, \r\n, or \t in the string to be encrypted. In addition, using a quoted string literal, with or without escaped characters, works without a problem:

@"MyPassword"

See Also

See Recipe 3.32 ; see the "System.Cryptography Namespace," "MemoryStream Class," "ICryptoTransform Interface," and "RijndaelManaged Class" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section