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:
- Support any encryption algorithm that derives from
SymmetricAlgorithm
(i.e. DES, RC2, Rijndael, 3DES, etc.) - Support working with
Stream
objects directly (i.e. MemoryStream
, FileStream
, etc.) - 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)
{
if (input == null)
throw new ArgumentNullException("input");
IntPtr inputPtr = IntPtr.Zero;
try
{
inputPtr = Marshal.SecureStringToBSTR(input);
if (inputPtr == IntPtr.Zero)
throw new InvalidOperationException("Unable to allocate" +
"necessary unmanaged resources.");
char[] inputBuffer = new char[input.Length];
try
{
GCHandle handle = GCHandle.Alloc(inputBuffer,
GCHandleType.Pinned);
try
{
Marshal.Copy(inputPtr, inputBuffer, 0, input.Length);
}
finally
{
handle.Free();
}
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
{
if (inputPtr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(inputPtr);
}
}
public void Decrypt(byte[] input, out SecureString output, byte[] key,
byte[] iv)
{
byte[] decryptedBuffer = null;
try
{
decryptedBuffer = Decrypt(input, key, iv);
char[] outputBuffer = null;
try
{
outputBuffer = _utf8.GetChars(decryptedBuffer);
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)
{
MemoryStream memStream = new MemoryStream();
CryptoStream cryptStream = new CryptoStream(memStream,
CryptoTransform, CryptoStreamMode.Write);
cryptStream.Write(input, 0, input.Length);
cryptStream.FlushFinalBlock();
memStream.Position = 0;
byte[] result = memStream.ToArray();
memStream.Close();
cryptStream.Close();
return result;
}
private void TransformStream(bool encrypt, ref Stream input,
ref Stream output, byte[] key, byte[] iv)
{
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");
byte[] inputBuffer = new byte[input.Length - input.Position];
input.Read(inputBuffer, 0, inputBuffer.Length);
byte[] outputBuffer = encrypt ? Encrypt(inputBuffer, key, iv)
: Decrypt(inputBuffer, key, iv);
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();
string testString = "My string test";
string encryptedString = sc.Encrypt(testString);
string decryptedString = sc.Decrypt(encryptedString);
Debug.Assert(testString == decryptedString);
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');
byte[] encryptedSecureString = sc.Encrypt(inputSecureString);
SecureString outputSecureString;
sc.Decrypt(encryptedSecureString, out outputSecureString);
string outputSecureStringValue;
IntPtr ptr = IntPtr.Zero;
try
{
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
{
if (ptr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(ptr);
}
Debug.Assert("A SecureString Test" == outputSecureStringValue);
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();
byte[] buffer = new byte[testStream.Length];
testStream.Position = 0;
testStream.Read(buffer, 0, buffer.Length);
string testStreamContent = utf8.GetString(buffer);
testStream.Close();
buffer = new byte[outputStream.Length];
outputStream.Position = 0;
outputStream.Read(buffer, 0, buffer.Length);
string outputStreamContent = utf8.GetString(buffer);
outputStream.Close();
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).