Utility Library Progress

9/8/2011

Between losing power for a week, being without internet for another week, and having family in town for yet another week, I've had no chance to post this. Nor have I had a chance to post a number of things that I've been meaning to. Hopefully that will change here. So if you've been following my utility library's progress, you'll know that I'm doing a major rework of a number of classes, I've been going in and rewriting my private unit tests so I can actually release them to everyone, and I've been clearing out old code that I no longer use or plan to maintain. This means that if you plan on going from the current 2.2 to what will become 3.0, you'll need to make some changes to your code.

For example, let's look at some encryption code. Before if you wanted to encrypt something you would have to call the static function Utilities.Encryption.AESEncryption.Encrypt. It only took in a string and would only encrypt it using AES:

   1: /// <summary>
   2: /// Encrypts a string
   3: /// </summary>
   4: /// <param name="PlainText">Text to be encrypted</param>
   5: /// <param name="Password">Password to encrypt with</param>
   6: /// <param name="Salt">Salt to encrypt with</param>
   7: /// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
   8: /// <param name="PasswordIterations">Number of iterations to do</param>
   9: /// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
  10: /// <param name="KeySize">Can be 128, 192, or 256</param>
  11: /// <returns>An encrypted string</returns>
  12: public static string Encrypt(string PlainText, string Password,
  13:     string Salt = "Kosher", string HashAlgorithm = "SHA1",
  14:     int PasswordIterations = 2, string InitialVector = "OFRna73m*aze01xY",
  15:     int KeySize = 256)
  16: {
  17:     if (string.IsNullOrEmpty(PlainText))
  18:         return "";
  19:     if (string.IsNullOrEmpty(Password))
  20:         throw new ArgumentNullException("Password");
  21:     if(string.IsNullOrEmpty(Salt))
  22:         throw new ArgumentNullException("Salt");
  23:     if(string.IsNullOrEmpty(HashAlgorithm))
  24:         throw new ArgumentNullException("HashAlgorithm");
  25:     if (string.IsNullOrEmpty(InitialVector))
  26:         throw new ArgumentNullException("InitialVector");
  27:     byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
  28:     byte[] SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
  29:     byte[] PlainTextBytes = Encoding.UTF8.GetBytes(PlainText);
  30:     PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
  31:     byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
  32:     RijndaelManaged SymmetricKey = new RijndaelManaged();
  33:     SymmetricKey.Mode = CipherMode.CBC;
  34:     byte[] CipherTextBytes = null;
  35:     using (ICryptoTransform Encryptor = SymmetricKey.CreateEncryptor(KeyBytes, InitialVectorBytes))
  36:     {
  37:         using (MemoryStream MemStream = new MemoryStream())
  38:         {
  39:             using (CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write))
  40:             {
  41:                 CryptoStream.Write(PlainTextBytes, 0, PlainTextBytes.Length);
  42:                 CryptoStream.FlushFinalBlock();
  43:                 CipherTextBytes = MemStream.ToArray();
  44:                 MemStream.Close();
  45:                 CryptoStream.Close();
  46:             }
  47:         }
  48:     }
  49:     SymmetricKey.Clear();
  50:     return Convert.ToBase64String(CipherTextBytes);
  51: }

In fact you had to use different static functions to use other encryption standards (DES, TripleDES, etc). And each of those functions took in different parameters:

DES:

   1: public static string Encrypt(string Input, string Key)

vs AES:

   1: public static string Encrypt(string PlainText, string Password,
   2:             string Salt = "Kosher", string HashAlgorithm = "SHA1",
   3:             int PasswordIterations = 2, string InitialVector = "OFRna73m*aze01xY",
   4:             int KeySize = 256)

With the newer code though things are different. Instead you can encrypt a string doing this:

   1: string Data = "This is a test of the system.";
   2: Data=Data.Encrypt("Babysfirstkey");

Using this function:

   1: /// <summary>
   2: /// Encrypts a string
   3: /// </summary>
   4: /// <param name="Data">Text to be encrypted</param>
   5: /// <param name="Key">Password to encrypt with</param>
   6: /// <param name="AlgorithmUsing">Algorithm to use for encryption (defaults to AES)</param>
   7: /// <param name="Salt">Salt to encrypt with</param>
   8: /// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
   9: /// <param name="PasswordIterations">Number of iterations to do</param>
  10: /// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
  11: /// <param name="KeySize">Can be 64 (DES only), 128 (AES), 192 (AES and Triple DES), or 256 (AES)</param>
  12: /// <param name="EncodingUsing">Encoding that the original string is using (defaults to UTF8)</param>
  13: /// <returns>An encrypted string (Base 64 string)</returns>
  14: public static string Encrypt(this string Data, string Key,
  15:     Encoding EncodingUsing = null,
  16:     SymmetricAlgorithm AlgorithmUsing = null, string Salt = "Kosher",
  17:     string HashAlgorithm = "SHA1", int PasswordIterations = 2,
  18:     string InitialVector = "OFRna73m*aze01xY", int KeySize = 256)
  19: {
  20:     if (string.IsNullOrEmpty(Data))
  21:         return "";
  22:     return Data.ToByteArray(EncodingUsing).Encrypt(Key, AlgorithmUsing, Salt, HashAlgorithm, PasswordIterations, InitialVector, KeySize).ToBase64String();
  23: }
  24:  
  25: /// <summary>
  26: /// Encrypts a byte array
  27: /// </summary>
  28: /// <param name="Data">Data to be encrypted</param>
  29: /// <param name="Key">Password to encrypt with</param>
  30: /// <param name="AlgorithmUsing">Algorithm to use for encryption (defaults to AES)</param>
  31: /// <param name="Salt">Salt to encrypt with</param>
  32: /// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
  33: /// <param name="PasswordIterations">Number of iterations to do</param>
  34: /// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
  35: /// <param name="KeySize">Can be 64 (DES only), 128 (AES), 192 (AES and Triple DES), or 256 (AES)</param>
  36: /// <returns>An encrypted byte array</returns>
  37: public static byte[] Encrypt(this byte[] Data, string Key,
  38:     SymmetricAlgorithm AlgorithmUsing = null, string Salt = "Kosher",
  39:     string HashAlgorithm = "SHA1", int PasswordIterations = 2,
  40:     string InitialVector = "OFRna73m*aze01xY", int KeySize = 256)
  41: {
  42:     if (Data == null)
  43:         return null;
  44:     if (AlgorithmUsing == null)
  45:         AlgorithmUsing = new RijndaelManaged();
  46:     if (string.IsNullOrEmpty(Key))
  47:         throw new ArgumentNullException("Key");
  48:     if (string.IsNullOrEmpty(Salt))
  49:         throw new ArgumentNullException("Salt");
  50:     if (string.IsNullOrEmpty(HashAlgorithm))
  51:         throw new ArgumentNullException("HashAlgorithm");
  52:     if (string.IsNullOrEmpty(InitialVector))
  53:         throw new ArgumentNullException("InitialVector");
  54:     using (PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Key, Salt.ToByteArray(), HashAlgorithm, PasswordIterations))
  55:     {
  56:         using (SymmetricAlgorithm SymmetricKey = AlgorithmUsing)
  57:         {
  58:             SymmetricKey.Mode = CipherMode.CBC;
  59:             byte[] CipherTextBytes = null;
  60:             using (ICryptoTransform Encryptor = SymmetricKey.CreateEncryptor(DerivedPassword.GetBytes(KeySize / 8), InitialVector.ToByteArray()))
  61:             {
  62:                 using (MemoryStream MemStream = new MemoryStream())
  63:                 {
  64:                     using (CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write))
  65:                     {
  66:                         CryptoStream.Write(Data, 0, Data.Length);
  67:                         CryptoStream.FlushFinalBlock();
  68:                         CipherTextBytes = MemStream.ToArray();
  69:                         MemStream.Close();
  70:                         CryptoStream.Close();
  71:                     }
  72:                 }
  73:             }
  74:             SymmetricKey.Clear();
  75:             return CipherTextBytes;
  76:         }
  77:     }
  78: }

Well, in reality 2 functions, plus there are a number of calls to other extension methods that I've added called through out. Anyway, with the newer code most of the static functions have been moved to being extension methods (where it makes sense anyway). On top of that many of the functions now work on multiple data types (the encryption functions now work on strings and byte arrays for example). I've also tried to give decent default values for everything (when I can) and tried to make things a bit more generic. For instance the one encryption method above does AES, DES, and TripleDES (along with RC2 and any other class that inherits from SymmetricAlgorithm). All you do is pass in the algorithm class you want to use and it handles the rest (and if you don't pass in anything it defaults to AES). This should make using the code much simpler and more succinct than it was in the past.

The same thing is going on for a number of classes (FileManager is now a series of extensions for the FileInfo and DirectoryInfo classes, Serialization class is now extension methods added onto byte arrays, XmlDocuments, etc). And in each of these instances I'm trying to reduce the amount of code that you will need to write. At least for me it's easier to write:

   1: TestClass TestItem=new TestClass();
   2: TestItem.ToXML(@".\Testing\Test.xml");

than

   1: TestClass TestObject=new TestClass();
   2: Utilities.IO.Serialization.ObjectToXML(TestObject,@".\Testing\Test.xml");

Plus it gives it a nicer fluent interface feel which is easier to follow as I try to make sure that everything returns some value that you can act on. Anyway, if you're at all interested in influencing the direction that I go with this then drop into the CodePlex site and let me know your opinion in the discussions section and next time I'll be starting a series on building a Dependency Injection library.



Comments