A simple way to encrypt query strings

Query strings are used to carry information. We might need to obfuscate them and provide some basic security without writing some elaborate encryption mechanism. Some standard ways of obfuscating, you will find is to Base64Encode the query string(remember to Url encode the resulting string as it is going to be on a url!):

     string data="query string";
     string encodedData = String.Empty;
       try
       {
           byte[] data_byte = Encoding.UTF8.GetBytes(data);
           encodedData = HttpUtility.UrlEncode(Convert.ToBase64String(data_byte));
        }
        catch (Exception exception)
        {
           //Log exception
        }
        return encodedData;

While this works, it provides only obfuscation, but no security. Anyone can do the reverse:

        string data="encoded data";
        string decodedData = String.Empty;
        try
        {
             byte[] data_byte = Convert.FromBase64String(HttpUtiltity.UrlDecode(data));
             decodedData = Encoding.UTF8.GetString(data_byte);
        }
        catch (Exception exception)
        {
             //Log exception
         }
         return decodedData;

A little more advanced method is to use PasswordDeriveBytes class in .NET to do proper encryption. This has since been replaced with Rfc2898DeriveBytes in .NET 2.0. The reason you would want to use Rfc2898DeriveBytes is because it meets the PKCS #5 standard, which PasswordDerrivedBytes, does not. Both of them are present in System.Security.Cryptography namespace.

What we need to do is:

  • Generate the encryption key and IV(Initialization Vector) from the password and salt.
  • Create a new instance of the encryptor with the key and IV.
  • Encrypt the query string.

We are not done yet. Improper use of Rfc2898DeriveBytes will result in a performance impact on large sets of data. The cause is the call to GetBytes(int32) method on Rfc2898DeriveBytes class. It uses a pseudo-random number generator based on HMACSHA1. GetBytes initializes a new instance of HMAC is the killer in terms of performance. But the redeeming factor is, subsequent calls to GetBytes does not need to do this initialization.

So we change our implementation to do this expensive call once. Since there is no need to create a new instance of Rfc2898DeriveBytes for each call to encrypt, we will use the same key but change the IV for each message. So remove Rfc2898DeriveBytes from the Encrypt method and pass along the key and IV instead of the password and salt.

Putting all this together here is the final code (fast and efficient):

      public static class MyHelper
      {

          #region Private variables and Constants
          private const string ENCRYPTION_KEY = "Wx0Te1rc0pA";
          // Generated from Pass Phrase  "xxxxxxxx12012010" or some pass phrase that you can remember from
          // https://www.bigbiz.com/genkey.html Key Generator Site

          private const string QUERY_PARTS_DELIMITOR = "&";
          private const string QUERY_PARAMS_DELIMITOR = "=";

          ///
          /// The salt value used to strengthen the encryption.
          ///
          private readonly static byte[] SALT = Encoding.ASCII.GetBytes(ENCRYPTION_KEY);
          private readonly static byte[] key;
          private readonly static byte[] iv;
          private static readonly Rfc2898DeriveBytes keyGenerator;

          #endregion

         #region Constructor

         static MyHelper()
         {
            //We create the Rfc2898DerveBytes once as
            //Rfc2898DeriveBytes uses a pseudo-random number generator based on HMACSHA1. 
            //When calling GetBytes it initializes a new instance of HMAC which takes some time.
            //Subsequent calls to GetBytes does not need to do this initialization.
            keyGenerator =new Rfc2898DeriveBytes(ENCRYPTION_KEY,SALT);
            key = keyGenerator.GetBytes(32);
            //Generate Initialization Vector - This will be less expensive as we have already intialized Rfc2898DeriveBytes
            iv = keyGenerator.GetBytes(16);
        }

       #endregion

       #region Encrypt/Decrypt Methods

        /// 
        /// Encrypts any string using the Rijndael algorithm.
        /// 
        /// The string to encrypt.
        /// The name of the query string paramater
        /// A Base64 encrypted string.
        public static string Encrypt(string inputText, string queryStringParam)
        {
            //Create a new RijndaelManaged cipher for the symmetric algorithm from the key and iv
            RijndaelManaged rijndaelCipher = new RijndaelManaged {Key = key, IV = iv};

            byte[] plainText = Encoding.Unicode.GetBytes(inputText);
            
            using (ICryptoTransform encryptor = rijndaelCipher.CreateEncryptor())
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(plainText, 0, plainText.Length);
                        cryptoStream.FlushFinalBlock();
                        return "?" + queryStringParam + "=" + Convert.ToBase64String(memoryStream.ToArray());
                    }
                }
            }
        }

        /// 
        /// Decrypts a previously encrypted string.
        /// 
        /// The encrypted string to decrypt.
        /// A decrypted string.
        public static string Decrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] encryptedData = Convert.FromBase64String(inputText);
            
            using (ICryptoTransform decryptor = rijndaelCipher.CreateDecryptor(key, iv))
            {
                using (MemoryStream memoryStream = new MemoryStream(encryptedData))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] plainText = new byte[encryptedData.Length];
                        int decryptedCount = cryptoStream.Read(plainText, 0, plainText.Length);
                        return Encoding.Unicode.GetString(plainText, 0, decryptedCount);
                    }
                }
            }
        }

        #endregion

       }

The only draw back is this doesn’t give you full security. This method will provide basic security in terms of stopping the hacker from deterministically manipulating the encrypted string. So it is a more a deterrence than a verification strategy. We can tell if someone tampers with the encrypted string only if the decryption ‘fails’ to decrypt to something you recognize or expect.

Social tagging: > > >

Comments are closed.

%d bloggers like this: