Click here to Skip to main content
6,305,776 members and growing! (17,173 online)
Email Password   helpLost your password?
General Programming » Cryptography & Security » Security     Intermediate License: The Code Project Open License (CPOL)

Generic SymmetricAlgorithm Helper

By Tony Selke

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.)
C# 2.0, Windows, .NET 2.0, Visual Studio, Dev
Posted:25 Sep 2007
Updated:15 Nov 2007
Views:19,120
Bookmarked:43 times
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
12 votes for this article.
Popularity: 5.02 Rating: 4.66 out of 5
1 vote, 8.3%
1

2
1 vote, 8.3%
3
2 votes, 16.7%
4
8 votes, 66.7%
5

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)

About the Author

Tony Selke


Member
Tony Selke is an independant consultant who has spent the last 16 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.
Occupation: Architect
Company: Wyvern Software
Location: United States United States

Other popular Cryptography & Security articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 20 of 20 (Total in Forum: 20) (Refresh)FirstPrevNext
GeneralMissing validation of legal key size Pinmemberalexruf23:20 15 Feb '09  
AnswerRe: Missing validation of legal key size PinmemberTony Selke4:15 17 Feb '09  
General.Net\C# 1.1 Version PinmemberDennis McMahon12:07 16 Nov '07  
AnswerRe: .Net\C# 1.1 Version PinmemberTony Selke13:37 16 Nov '07  
GeneralRe: .Net\C# 1.1 Version PinmemberDennis McMahon13:46 16 Nov '07  
GeneralRe: .Net\C# 1.1 Version PinmemberTony Selke13:49 16 Nov '07  
GeneralRe: .Net\C# 1.1 Version PinmemberDennis McMahon14:05 16 Nov '07  
GeneralSuggestions Pinmemberellarr4:17 11 Nov '07  
GeneralRe: Suggestions PinmemberTony Selke13:58 11 Nov '07  
GeneralRe: Suggestions Pinmemberellarr2:49 12 Nov '07  
GeneralRe: Suggestions PinmemberTony Selke3:06 12 Nov '07  
GeneralRe: Suggestions Pinmemberellarr4:39 12 Nov '07  
AnswerRe: Suggestions PinmemberTony Selke8:13 12 Nov '07  
QuestionSafety/Security Pinmemberawaescher23:12 7 Nov '07  
AnswerRe: Safety/Security PinmemberTony Selke3:20 8 Nov '07  
GeneralVB.NET version? PinmemberAliasGoesHere11:29 24 Oct '07  
AnswerRe: VB.NET version? PinmemberTony Selke4:23 25 Oct '07  
GeneralOne other related encryption article in CodeProject ... PinmemberNathan Blomquist16:32 27 Sep '07  
GeneralRe: One other related encryption article in CodeProject ... PinmemberTony Selke3:20 28 Sep '07  
GeneralGeneric Type Constraints PinmemberSam Page5:04 27 Sep '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 15 Nov 2007
Editor: Sean Ewington
Copyright 2007 by Tony Selke
Everything else Copyright © CodeProject, 1999-2009
Web18 | Advertise on the Code Project