Click here to Skip to main content
Click here to Skip to main content

Generic SymmetricAlgorithm Helper

, 15 Nov 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
A generic helper class that exposes simplified Encrypt and Decrypt functionality for strings, byte arrays and streams for any SymmetricAlgorithm derivative (DES, RC2, Rijndael, TripleDES, etc.)

Introduction

Back in June 2004, I wrote a short article titled Making TripleDES Simple in Visual Basic .NET, in which I provided a simple helper class that allowed you to easily encrypt and decrypt strings or byte arrays using the TripleDES algorithm. To this day, I get messages from people who find and use that code (available in both VB.NET and C#).

Every once in a while, I get people who want to use some other encryption algorithm or who want to encrypt entire files. I usually just pointed them back to that first example and told them that they needed to change the provider type or convert their file streams to byte arrays (and back). This morning, I was motivated to update my old example to do three more things:

  1. Support any encryption algorithm that derives from SymmetricAlgorithm (i.e. DES, RC2, Rijndael, 3DES, etc.)
  2. Support working with Stream objects directly (i.e. MemoryStream, FileStream, etc.)
  3. Expose the GenerateKey() and GenerateIV() methods so that you could easily have the class provide you with random values for the key and vector

This new and improved implementation is the result of those updates. The code is substantially identical to the original article, but updated to take advantage of the new generics support in .NET 2.0. I posted this as a new article only because it is a new class and because I wanted to change the category from VB.NET to C#.

About the Code

The SymmetricCryptography<T> class is a generic which enforces an explicit constraint on the types that can be provided for T. In this case, T must be derived from the abstract System.Security.Cryptography.SymmetricAlgorithm class found in the .NET Framework and it must provide a default (parameterless) constructor. SymmetricAlgorithm is one of the base classes from which the following System.Security.Cryptography providers derive:

  • DESCryptoServiceProvider
  • RC2CryptoServiceProvider
  • RijndaelManaged
  • TripleDESCryptoServiceProvider

As a result, any of the current .NET cryptography providers listed above can be used with the SymmetricCryptography<T> class, as well as any future providers that are incorporated into the .NET Framework (provided Microsoft continues to derive from System.Security.Cryptography.SymmetricAlgorithm).

The Code

This is the complete text of the SymmetricCryptography<T> class, which you can also download from the link at the top of the article:

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

namespace Utilities.Crypto
{
    class SymmetricCryptography<T> where T : SymmetricAlgorithm, new()
    {
        #region Fields

        private T _provider = new T();
        private UTF8Encoding _utf8 = new UTF8Encoding();

        #endregion Fields

        #region Properties

        private byte[] _key;
        public byte[] Key
        {
            get { return _key; }
            set { _key = value; }
        }

        private byte[] _iv;
        public byte[] IV
        {
            get { return _iv; }
            set { _iv = value; }
        }

        #endregion Properties

        #region Constructors

        public SymmetricCryptography()
        {
            _provider.GenerateKey();
            _key = _provider.Key;
            _provider.GenerateIV();
            _iv = _provider.IV;
        }

        public SymmetricCryptography(byte[] key, byte[] iv)
        {
            _key = key;
            _iv = iv;
        }

        #endregion Constructors

        #region Byte Array Methods

        public byte[] Encrypt(byte[] input)
        {
            return Encrypt(input, _key, _iv);
        }

        public byte[] Decrypt(byte[] input)
        {
            return Decrypt(input, _key, _iv);
        }
        
        public byte[] Encrypt(byte[] input, byte[] key, byte[] iv)
        {
            return Transform(input,
                   _provider.CreateEncryptor(key, iv));
        }

        public byte[] Decrypt(byte[] input, byte[] key, byte[] iv)
        {
            return Transform(input,
                   _provider.CreateDecryptor(key, iv));
        }

        #endregion Byte Array Methods

        #region String Methods

        public string Encrypt(string text)
        {
            return Encrypt(text, _key, _iv);
        }

        public string Decrypt(string text)
        {
            return Decrypt(text, _key, _iv);
        }

        public string Encrypt(string text, byte[] key, byte[] iv)
        {
            byte[] output = Transform(_utf8.GetBytes(text),
                            _provider.CreateEncryptor(key, iv));
            return Convert.ToBase64String(output);
        }

        public string Decrypt(string text, byte[] key, byte[] iv)
        {
            byte[] output = Transform(Convert.FromBase64String(text),
                            _provider.CreateDecryptor(key, iv));
            return _utf8.GetString(output);
        }

        #endregion String Methods
        
        #region SecureString Methods
        
        public byte[] Encrypt(SecureString input)
        {
            return Encrypt(input, _key, _iv);
        }

        public void Decrypt(byte[] input, out SecureString output)
        {
            Decrypt(input, out output, _key, _iv);
        }

        public byte[] Encrypt(SecureString input, byte[] key, byte[] iv)
        {
            // defensive argument checking
            if (input == null)
                throw new ArgumentNullException("input");

            IntPtr inputPtr = IntPtr.Zero;

            try
            {
                // copy the SecureString to an unmanaged BSTR
                // and get back the pointer to the memory location
                inputPtr = Marshal.SecureStringToBSTR(input);
                if (inputPtr == IntPtr.Zero)
                    throw new InvalidOperationException("Unable to allocate" +
                        "necessary unmanaged resources.");

                char[] inputBuffer = new char[input.Length];

                try
                {
                    // pin the buffer array so the GC doesn't move it while we
                    // are doing an unmanaged memory copy, but make sure we 
                    // release the pin when we are done so that the CLR can do
                    // its thing later
                    GCHandle handle = GCHandle.Alloc(inputBuffer, 
                        GCHandleType.Pinned);
                    try
                    {
                        Marshal.Copy(inputPtr, inputBuffer, 0, input.Length);
                    }
                    finally
                    {
                        handle.Free();
                    }

                    // encode the input as UTF8 first so that we have a
                    // way to explicitly "flush" the byte array afterwards
                    byte[] utf8Buffer = _utf8.GetBytes(inputBuffer);
                    try
                    {
                        return Encrypt(utf8Buffer, key, iv);
                    }
                    finally
                    {
                        Array.Clear(utf8Buffer, 0, utf8Buffer.Length);
                    }
                }
                finally
                {
                    Array.Clear(inputBuffer, 0, inputBuffer.Length);
                }
            }
            finally
            {
                // because we are using unmanaged resources, we *must*
                // explicitly deallocate those resources ourselves
                if (inputPtr != IntPtr.Zero)
                    Marshal.ZeroFreeBSTR(inputPtr);
            }
        }

        public void Decrypt(byte[] input, out SecureString output, byte[] key,
            byte[] iv)
        {
            byte[] decryptedBuffer = null;

            try
            {
                // do our normal decryption of a byte array
                decryptedBuffer = Decrypt(input, key, iv);

                char[] outputBuffer = null;
                
                try
                {
                    // convert the decrypted array to an explicit
                    // character array that we can "flush" later
                    outputBuffer = _utf8.GetChars(decryptedBuffer);

                    // Create the result and copy the characters
                    output = new SecureString();
                    try
                    {
                        for (int i = 0; i < outputBuffer.Length; i++)
                            output.AppendChar(outputBuffer[i]);
                        return;
                    }
                    finally
                    {
                        output.MakeReadOnly();
                    }
                }
                finally
                {
                    if (outputBuffer != null)
                        Array.Clear(outputBuffer, 0, outputBuffer.Length);
                }
            }
            finally
            {
                if (decryptedBuffer != null)
                    Array.Clear(decryptedBuffer, 0, decryptedBuffer.Length);
            }
        }

        #endregion SecureString Methods

        #region Stream Methods

        public void Encrypt(Stream input, Stream output)
        {
            Encrypt(input, output, _key, _iv);
        }

        public void Decrypt(Stream input, Stream output)
        {
            Decrypt(input, output, _key, _iv);
        }

        public void Encrypt(Stream input, Stream output, byte[] key,
            byte[] iv)
        {
            TransformStream(true, ref input, ref output, key, iv);
        }

        public void Decrypt(Stream input, Stream output, byte[] key,
            byte[] iv)
        {
            TransformStream(false, ref input, ref output, key, iv);
        }

        #endregion Stream Methods

        #region Private Methods

        private byte[] Transform(byte[] input, 
                       ICryptoTransform CryptoTransform)
        {
            // create the necessary streams
            MemoryStream memStream = new MemoryStream();
            CryptoStream cryptStream = new CryptoStream(memStream, 
                         CryptoTransform, CryptoStreamMode.Write);
            // transform the bytes as requested
            cryptStream.Write(input, 0, input.Length);
            cryptStream.FlushFinalBlock();
            // Read the memory stream and
            // convert it back into byte array
            memStream.Position = 0;
            byte[] result = memStream.ToArray();
            // close and release the streams
            memStream.Close();
            cryptStream.Close();
            // hand back the encrypted buffer
            return result;
        }

        private void TransformStream(bool encrypt, ref Stream input, 
            ref Stream output, byte[] key, byte[] iv)
        {
            // defensive argument checking
            if (input == null)
                throw new ArgumentNullException("input");
            if (output == null)
                throw new ArgumentNullException("output");
            if (!input.CanRead)
                throw new ArgumentException("Unable to read from the input" +
                    "Stream.", "input");
            if (!output.CanWrite)
                throw new ArgumentException("Unable to write to the output" +
                    "Stream.", "output");
            // make the buffer just large enough for 
            // the portion of the stream to be processed
            byte[] inputBuffer = new byte[input.Length - input.Position];
            // read the stream into the buffer
            input.Read(inputBuffer, 0, inputBuffer.Length);
            // transform the buffer
            byte[] outputBuffer = encrypt ? Encrypt(inputBuffer, key, iv) 
                                          : Decrypt(inputBuffer, key, iv);
            // write the transformed buffer to our output stream 
            output.Write(outputBuffer, 0, outputBuffer.Length);
        }

        #endregion Private Methods
    }
}

Using the SymmetricCryptography<T> class in your code is as simple as shown below. You can download the test code from the link at the top of the article.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Security.Cryptography;
using Utilities.Crypto;
using System.Security;
using System.Runtime.InteropServices;

namespace SymmetricAlgorithmHelper
{
    class Program
    {
        static void Main(string[] args)
        {
            SymmetricCryptography<tripledescryptoserviceprovider /> sc =
                new SymmetricCryptography<tripledescryptoserviceprovider />();

            UTF8Encoding utf8 = new UTF8Encoding();

            // create a string, encrypt it, decrypt it 
            // and compare the new to the original
            string testString = "My string test";
            string encryptedString = sc.Encrypt(testString);
            string decryptedString = sc.Decrypt(encryptedString);
            Debug.Assert(testString == decryptedString);



            // create a SecureString, encrypt it, decrypt
            // it and compare the new to the original
            SecureString inputSecureString = new SecureString();
            inputSecureString.AppendChar('A');
            inputSecureString.AppendChar(' ');
            inputSecureString.AppendChar('S');
            inputSecureString.AppendChar('e');
            inputSecureString.AppendChar('c');
            inputSecureString.AppendChar('u');
            inputSecureString.AppendChar('r');
            inputSecureString.AppendChar('e');
            inputSecureString.AppendChar('S');
            inputSecureString.AppendChar('t');
            inputSecureString.AppendChar('r');
            inputSecureString.AppendChar('i');
            inputSecureString.AppendChar('n');
            inputSecureString.AppendChar('g');
            inputSecureString.AppendChar(' ');
            inputSecureString.AppendChar('T');
            inputSecureString.AppendChar('e');
            inputSecureString.AppendChar('s');
            inputSecureString.AppendChar('t');
            // encrypt the above SecureString
            byte[] encryptedSecureString = sc.Encrypt(inputSecureString);
            // decrypt the data back to a SecureString
            SecureString outputSecureString;
            sc.Decrypt(encryptedSecureString, out outputSecureString);
            
            // this next block is a quick & dirty way to get 
            // a string value out of a SecureString and is
            // not intended to be a best-practice for working
            // with the SecureString objects
            string outputSecureStringValue;
            IntPtr ptr = IntPtr.Zero;
            try
            {
                // copy the SecureString to an unmanaged BSTR
                // and get back the pointer to the memory location
                ptr = Marshal.SecureStringToBSTR(outputSecureString);
                char[] SecureStringBuffer = 
                    new char[outputSecureString.Length];
                GCHandle handle = GCHandle.Alloc(SecureStringBuffer, 
                    GCHandleType.Pinned);
                try
                {
                    Marshal.Copy(ptr, SecureStringBuffer, 0, 
                        outputSecureString.Length);
                }
                finally
                {
                    handle.Free();
                }
                outputSecureStringValue = new string(SecureStringBuffer);
            }
            finally
            {
                // because we are using unmanaged resources, we *must*
                // explicitly deallocate those resources ourselves
                if (ptr != IntPtr.Zero)
                    Marshal.ZeroFreeBSTR(ptr);
            }
            // validate that what we put in is what we got back
            Debug.Assert("A SecureString Test" == outputSecureStringValue);


            // Open a file stream, encrypt it to a memory stream,
            // decrypt it to a new file stream.  You do not need to
            // use a MemoryStream in-between; you could go straight
            // from a FileStream to a FileStream.  I only use the 
            // MemoryStream to show that the functionality works
            // with any type of Stream.
            FileStream testStream = new FileStream
                (@"C:\TestFile.txt", FileMode.Open, FileAccess.Read);
            MemoryStream encryptedStream = new MemoryStream();
            sc.Encrypt(testStream, encryptedStream);
            FileStream outputStream = new FileStream
                (@"C:\TestFile_OUT.txt", FileMode.Create, 
                    FileAccess.ReadWrite);
            encryptedStream.Position = 0;
            sc.Decrypt(encryptedStream, outputStream);
            encryptedStream.Close();

            // get the content of the original text file
            byte[] buffer = new byte[testStream.Length];
            testStream.Position = 0;
            testStream.Read(buffer, 0, buffer.Length);
            string testStreamContent = utf8.GetString(buffer);
            testStream.Close();

            // get the content of the decrypted text file
            buffer = new byte[outputStream.Length];
            outputStream.Position = 0;
            outputStream.Read(buffer, 0, buffer.Length);
            string outputStreamContent = utf8.GetString(buffer);
            outputStream.Close();

            // compare the two
            Debug.Assert(testStreamContent == outputStreamContent);

        }
    }
}

History

  • 12-NOV-2007: Added support for encrypting from and decrypting to System.Security.SecureString objects. Thanks to ellarr for his suggestion and source code!
  • 25-SEP-2007: Initial release (actually an update and enhancement of my original article: Making TripleDES Simple in Visual Basic .NET).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Tony Selke
Architect Wyvern Software
United States United States
Tony Selke is an independant consultant who has spent the last 20 years working with Microsoft technologies (VB, VC++, ASP, J++, C#, VB.NET, SQL Server, etc.) to develop solutions used in all kinds of market verticals (industrial, pharmaceutical, financial, marketing, multimedia, educational, telecommunications, etc.). He obtained his first MCSD certification in 1998 and his second in 2004, with an MCDBA in 2005. In addition, he has taught courses for MCSD certification students as well as programming classes at Penn State University.

Comments and Discussions

 
GeneralMissing validation of legal key size Pinmemberalexruf15-Feb-09 23:20 
AnswerRe: Missing validation of legal key size PinmemberTony Selke17-Feb-09 4:15 
General.Net\C# 1.1 Version PinmemberDennis McMahon16-Nov-07 12:07 
AnswerRe: .Net\C# 1.1 Version PinmemberTony Selke16-Nov-07 13:37 
GeneralRe: .Net\C# 1.1 Version PinmemberDennis McMahon16-Nov-07 13:46 
GeneralRe: .Net\C# 1.1 Version PinmemberTony Selke16-Nov-07 13:49 
GeneralRe: .Net\C# 1.1 Version PinmemberDennis McMahon16-Nov-07 14:05 
GeneralSuggestions Pinmemberellarr11-Nov-07 4:17 
Very nice class!
 
There are a couple changes I'd like to see if you're ever bored.
 
First, it would be nice if the TransformStream method used a fixed size buffer instead of allocating memory for the entire stream.
 
Second, I'd like to see some emphasis placed on protecting the unencrypted values in memory. For example, replacing the string type of the unencrypted parameters with SecureString or at least zeroing out the buffers used for unencrypted data (possibly by RtlZeroMemory).

GeneralRe: Suggestions PinmemberTony Selke11-Nov-07 13:58 
GeneralRe: Suggestions Pinmemberellarr12-Nov-07 2:49 
GeneralRe: Suggestions PinmemberTony Selke12-Nov-07 3:06 
GeneralRe: Suggestions Pinmemberellarr12-Nov-07 4:39 
AnswerRe: Suggestions PinmemberTony Selke12-Nov-07 8:13 
QuestionSafety/Security Pinmemberawaescher7-Nov-07 23:12 
AnswerRe: Safety/Security PinmemberTony Selke8-Nov-07 3:20 
QuestionVB.NET version? PinmemberAliasGoesHere24-Oct-07 11:29 
AnswerRe: VB.NET version? PinmemberTony Selke25-Oct-07 4:23 
GeneralOne other related encryption article in CodeProject ... PinmemberNathan Blomquist27-Sep-07 16:32 
GeneralRe: One other related encryption article in CodeProject ... PinmemberTony Selke28-Sep-07 3:20 
GeneralGeneric Type Constraints PinmemberSam Page27-Sep-07 5:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411022.1 | Last Updated 15 Nov 2007
Article Copyright 2007 by Tony Selke
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid