Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#
Article

Generic SymmetricAlgorithm Helper

Rate me:
Please Sign up or sign in to vote.
4.85/5 (15 votes)
15 Nov 2007CPOL2 min read 79.8K   1.2K   53   20
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:

C#
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.

C#
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)


Written By
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 Pin
alexruf15-Feb-09 22:20
alexruf15-Feb-09 22:20 
AnswerRe: Missing validation of legal key size Pin
Tony Selke17-Feb-09 3:15
Tony Selke17-Feb-09 3:15 
General.Net\C# 1.1 Version Pin
Dennis McMahon16-Nov-07 11:07
Dennis McMahon16-Nov-07 11:07 
AnswerRe: .Net\C# 1.1 Version Pin
Tony Selke16-Nov-07 12:37
Tony Selke16-Nov-07 12:37 
GeneralRe: .Net\C# 1.1 Version Pin
Dennis McMahon16-Nov-07 12:46
Dennis McMahon16-Nov-07 12:46 
GeneralRe: .Net\C# 1.1 Version Pin
Tony Selke16-Nov-07 12:49
Tony Selke16-Nov-07 12:49 
GeneralRe: .Net\C# 1.1 Version Pin
Dennis McMahon16-Nov-07 13:05
Dennis McMahon16-Nov-07 13:05 
GeneralSuggestions Pin
ellarr11-Nov-07 3:17
ellarr11-Nov-07 3:17 
GeneralRe: Suggestions Pin
Tony Selke11-Nov-07 12:58
Tony Selke11-Nov-07 12:58 
GeneralRe: Suggestions Pin
ellarr12-Nov-07 1:49
ellarr12-Nov-07 1:49 
GeneralRe: Suggestions Pin
Tony Selke12-Nov-07 2:06
Tony Selke12-Nov-07 2:06 
GeneralRe: Suggestions Pin
ellarr12-Nov-07 3:39
ellarr12-Nov-07 3:39 
AnswerRe: Suggestions Pin
Tony Selke12-Nov-07 7:13
Tony Selke12-Nov-07 7:13 
QuestionSafety/Security Pin
awaescher7-Nov-07 22:12
awaescher7-Nov-07 22:12 
AnswerRe: Safety/Security Pin
Tony Selke8-Nov-07 2:20
Tony Selke8-Nov-07 2:20 
QuestionVB.NET version? Pin
AliasGoesHere24-Oct-07 10:29
AliasGoesHere24-Oct-07 10:29 
AnswerRe: VB.NET version? Pin
Tony Selke25-Oct-07 3:23
Tony Selke25-Oct-07 3:23 
GeneralOne other related encryption article in CodeProject ... Pin
Nathan Blomquist27-Sep-07 15:32
Nathan Blomquist27-Sep-07 15:32 
GeneralRe: One other related encryption article in CodeProject ... Pin
Tony Selke28-Sep-07 2:20
Tony Selke28-Sep-07 2:20 
GeneralGeneric Type Constraints Pin
Sam Page27-Sep-07 4:04
Sam Page27-Sep-07 4:04 

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

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