Today I would like to write some aspects for security and encryption of sensitive data in our application. Because I am a huge fan of the PostSharp Framework, I will try to prepare two aspects. That will be EncryptData and DecryptData aspects. Also, I need to be sure that my aspects work very fast. So I will try to measure working aspects with the ANTS Performance Profiler 6 that I have.
So let me start with simple requirement:
The EncryptAspect and the DecryptAspect should use symmetric algorithms to encrypt and decrypt array of byte.
OK, so I will try prepare resolution for that requirement in this blog entry. In .NET Framework, we have four symmetric algorithms implemented. There are RijndelManaged (AES), DES, RC2, or TripleDES. I do not know which of that is the best for my resolution and I will try examine performance of them all using the ANTS Performance Profiler 6.
So now it is time for design. First, we need a generic class called CommonEncryptEngine that can use any of our four cryptography implementation. The design will be simple. We use a common generic class with a TEncryptEngine type parameter and two methods. Encrypt and Decrypt methods will be used for the encryption and decryption array of bytes. When we prepare the common engine class, we will prepare EncryptAttribute and DecryptAttribute based on PostSharp OnMethodBoundaryAspect with one parameter for the constructor. That parameter will be a type for the common class generic parameter TEncryptEngine. We will use reflection, and in the static constructor, we prepare a common crypt engine class.
OK, we have requirement and basic design. We can start some coding.
First will be the CommonEncryptEngine – common generic class.
namespace CryptoEngineAspects { using System.Security.Cryptography; public class CommonEncryptEngine<TEncryptEngine> where TEncryptEngine : SymmetricAlgorithm, new() { private readonly TEncryptEngine cryptEngine; private readonly ICryptoTransform encryptor; private readonly ICryptoTransform decryptor; public CommonEncryptEngine(byte[] iv = null, byte[] key = null) { cryptEngine = new TEncryptEngine(); if (iv != null && key != null) { cryptEngine.IV = iv; cryptEngine.Key = key; } else { cryptEngine.GenerateIV(); cryptEngine.GenerateKey(); } encryptor = cryptEngine.CreateEncryptor(); decryptor = cryptEngine.CreateDecryptor(); } public byte[] Encrypt(byte[] data) { return encryptor.TransformFinalBlock(data, 0, data.Length); } public byte[] Decrypt(byte[] data) { return decryptor.TransformFinalBlock(data, 0, data.Length); } } }
OK, so this is based common class implementation. We want to check if it works correctly, so we need some test classes. For example, the CommonEncryptEngineTests generic class will be my implementation of checking and performance tests. So we can start with two basic tests and implementation shown below.
using System.Diagnostics; namespace CryptoEngineAspects { using System.IO; using System.Security.Cryptography; using System.Text; public class CommonEncryptEngineTests<TEncryptEngine> where TEncryptEngine : SymmetricAlgorithm, new() { private readonly CommonEncryptEngine<TEncryptEngine> engine = new CommonEncryptEngine<TEncryptEngine>(); private void BaseTestTransformData(string stringData) { var data = ASCIIEncoding.ASCII.GetBytes(stringData); var encryptedData = engine.Encrypt(data); var decryptedData = engine.Decrypt(encryptedData); var retrunedData = ASCIIEncoding.ASCII.GetString(decryptedData); if (!stringData.Equals(retrunedData)) throw new InvalidDataException( string.Format( "Transformation with encrypt and decrypt failed for type: {0}.", typeof (TEncryptEngine))); } private void Test1() { BaseTestTransformData("Hello Common Encryption"); } private void Test2() { BaseTestTransformData("1 Bigger block of text.n" + "2 Bigger block of text.n" + "3 Bigger block of text.n" + "4 Bigger block of text.n" + "5 Bigger block of text.n"); } public void RunAllChecking() { Test1(); Test2(); } public long RunAllPerformance(int count) { var stopwatch = Stopwatch.StartNew(); for (int i = 0; i < count; i++) { Test1(); Test2(); } stopwatch.Stop(); return stopwatch.ElapsedMilliseconds; } } }
So, we have implementation and tests. We can now try how it works in some simple console applications.
namespace CryptoEngineAspects { using System.Security.Cryptography; class Program { static void Main() { new CommonEncryptEngineTests<RijndaelManaged>() .RunAllChecking(); new CommonEncryptEngineTests<DESCryptoServiceProvider>() .RunAllChecking(); new CommonEncryptEngineTests<RC2CryptoServiceProvider>() .RunAllChecking(); new CommonEncryptEngineTests<TripleDESCryptoServiceProvider>() .RunAllChecking(); } } }
OK, everything works correctly because we haven’t any exceptions, so we can now measure performance very easily, run a TestAllPerformance method from the test class, and write into console time for transform strings, for example, 100 000 times. So we can try to change a bit our console application and see the results.
namespace CryptoEngineAspects { using System; using System.Security.Cryptography; class Program { static void Main() { Console.WriteLine("Runs of 200 000 (2 per iteration) transformations."); Console.WriteLine("RijndaelManaged transforms take about {0} ms.", new CommonEncryptEngineTests<RijndaelManaged>() .RunAllPerformance(100000)); Console.WriteLine("DES transforms take about {0} ms.", new CommonEncryptEngineTests<DESCryptoServiceProvider>() .RunAllPerformance(100000)); Console.WriteLine("RC2 transforms take about {0} ms.", new CommonEncryptEngineTests<RC2CryptoServiceProvider>() .RunAllPerformance(100000)); Console.WriteLine("TripleDES transforms take about {0} ms.", new CommonEncryptEngineTests<TripleDESCryptoServiceProvider>() .RunAllPerformance(100000)); Console.ReadKey(); } } }
An output of performance tests is shown below.
Runs of 200 000 (2 per iteration) transformations. RijndaelManaged transforms take about 4818 ms. DES transforms take about 3439 ms. RC2 transforms take about 4240 ms. TripleDES transforms take about 6397 ms.
As you can see DES algorithm is the fastest one. And now, because we have implemented cryptography solutions, we can write two aspects.
namespace CryptoEngineAspects { using System; using System.Globalization; using System.Reflection; public class CryptographyAspectsBsse { private readonly object cryptographyEngine; private readonly MethodInfo encryptMethod; private readonly MethodInfo decryptMethod; public CryptographyAspectsBsse(Type cryptographyType) { Type cryptography = typeof(CommonEncryptEngine<>) .MakeGenericType(cryptographyType); cryptographyEngine = Activator .CreateInstance(cryptography, BindingFlags.Default, null, new object[]{null, null}, CultureInfo.CurrentCulture); encryptMethod = cryptography.GetMethod("Encrypt"); decryptMethod = cryptography.GetMethod("Decrypt"); } public byte[] Encrypt(byte[] data) { return (byte[])encryptMethod .Invoke(cryptographyEngine, new [] { data }); } public byte[] Decrypt(byte[] data) { return (byte[])decryptMethod .Invoke(cryptographyEngine, new[] { data }); } } }
Now we need host that store our engine and give us an Encrypt and a Decrypt methods to use with the same random IV and Key.
namespace CryptoEngineAspects { using System; using System.Runtime.CompilerServices; public class CryptographyAspectsHost { private static object locker = new object(); private static CryptographyAspectsBsse engine; [MethodImpl(MethodImplOptions.Synchronized)] public static CryptographyAspectsBsse GetInstance(Type cryptographyType) { lock (locker) { if (engine == null) engine = new CryptographyAspectsBsse(cryptographyType); return engine; } } } }
And at last we can prepare aspects.
namespace CryptoEngineAspects { using System; using System.Security.Cryptography; using PostSharp.Aspects; [Serializable] public class EncryptAttribute : OnMethodBoundaryAspect { [NonSerialized] private CryptographyAspectsBsse engine; public override void OnSuccess(MethodExecutionArgs args) { if (!(args.ReturnValue is byte[])) return; if(engine == null) engine = CryptographyAspectsHost .GetInstance(typeof(DESCryptoServiceProvider)); var data = (byte[])args.ReturnValue; args.ReturnValue = engine.Encrypt(data); } } }
namespace CryptoEngineAspects { using System; using System.Security.Cryptography; using PostSharp.Aspects; [Serializable] public class DecryptAttribute : OnMethodBoundaryAspect { [NonSerialized] private CryptographyAspectsBsse engine; public override void OnSuccess(MethodExecutionArgs args) { if (!(args.Arguments[0] is byte[])) return; if (engine == null) engine = CryptographyAspectsHost .GetInstance(typeof(DESCryptoServiceProvider)); var data = (byte[])args.Arguments[0]; args.ReturnValue = engine.Decrypt(data); } } }
OK, that is my solutions for Cryptography with the PostSharp Aspects. And last code I would like to show is a small test.
namespace CryptoEngineAspects { using System; using System.Text; class Program { [Encrypt] static byte[] EncryptTest(byte[] data) { return data; } [Decrypt] static byte[] DecryptTest(byte[] data) { return data; } static void Main() { Console.WriteLine( ASCIIEncoding.ASCII.GetString( DecryptTest( EncryptTest( ASCIIEncoding.ASCII.GetBytes("Hello Crypted World!"))))); Console.ReadKey(); } } }
And now it is time for conclusions. It is straightforward to secure sensitive data with symmetric encryption algorithms implemented in .NET Framework. The fastest one is DES. Of course, it is your decision to use encryption as the best algorithm for you. I think that the most secure is TripleDES, but my conclusion is that it is the slowest. So, probably AES implementation will be a good choice. This is a compromise with security and computer time. As you can see, the ANTS PP 6 is beneficial for checking and/or finding code issues and can be a beneficial tool. And you can also easily encapsulate solutions into the PostSharp aspects. I hope you enjoy this blog entry.
Best regards,
P ;).