Thursday, January 22, 2009

Obfuscating QueryString Parameters

I need to provide a link to a URL which contains some parameters I'd like to obfuscate. Specifically, within an automatically generated Email, instead of embedding a link /sendMessage.aspx?address=foo@bar.com&expiry=2009-01-22Z13:30 , I'd prefer /sendMessage.aspx?token=s0MEth1ngUnfath0mable!

Broken down, my requirements for obfuscation are:
  • Serialize an object which contains my parameters (address and expiry), so that it can be transmitted to a different server (i.e. the web server)
  • Encrypt the serialized bytes to prevent tampering
  • Make Quotable the encrypted bytes so that they can be included within a URL querystring (albeit after a suitable UrlEncode)


Key Generation

My security requirements are pretty basic, as I'm not trying to protect anything desperately important here - rather prevent basic tampering by spotting patterns in the quotable string. Therefore, I'll keep things as simple as possible and take some shortcuts, for example using a basic security algorithm (DES), and embedding the encryption key within my source code.

Firstly, we'll need to generate a key upon which the encryption and decryption can occur:

using System;
using System.IO;
using System.Security.Cryptography;
 
namespace ObfuscationTesting
{
    class Program
    {
        static void Main(string[] args)
        {
            // Generate a suitable key
            DESCryptoServiceProvider desCryptoServiceProvider =
                new DESCryptoServiceProvider();
            desCryptoServiceProvider.GenerateKey();
 
            // Represent key as a quotable string
            string base64EncodedKey =
                Convert.ToBase64String(desCryptoServiceProvider.Key);
 
            // Save to file
            File.WriteAllText(@"C:\key.txt", base64EncodedKey);
        }
    }
}


Obfuscator Class

Now, we'll create a static class that takes any serializable object and returns us a string which represents that object (and, naturally, allows us to re-create the original object from such a string):

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
 
namespace ObfuscationTesting
{
    public static class Obfsucator
    {
        // The key previously generated
        private const string KEY = "MXvUAmUobjA=";
 
        public static string Obfuscate(object targetObject)
        {
            // Binary serialize
            MemoryStream memoryStream = new MemoryStream();
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(memoryStream, targetObject);
            byte[] serialized = memoryStream.GetBuffer();
 
            // Encrypt
            DESCryptoServiceProvider desCryptoServiceProvider =
                new DESCryptoServiceProvider();
            desCryptoServiceProvider.Key = Convert.FromBase64String(KEY);
            desCryptoServiceProvider.IV = Convert.FromBase64String(KEY);
            byte[] encrypted = desCryptoServiceProvider
                .CreateEncryptor()
                .TransformFinalBlock(serialized, 0, serialized.Length);
 
            // Render as string
            string quotable = Convert.ToBase64String(encrypted);
 
            return quotable;
        }
 
        public static object Deobfuscate(string quotable)
        {
            // Retrieve the bytes from the quotable string
            byte[] encrypted = Convert.FromBase64String(quotable);
 
            // Decrypt
            DESCryptoServiceProvider desCryptoServiceProvider =
                new DESCryptoServiceProvider();
            desCryptoServiceProvider.Key = Convert.FromBase64String(KEY);
            desCryptoServiceProvider.IV = Convert.FromBase64String(KEY);
            byte[] serialized = desCryptoServiceProvider
                .CreateDecryptor()
                .TransformFinalBlock(encrypted, 0, encrypted.Length);
 
            // Deserialize
            MemoryStream memoryStream = new MemoryStream(serialized);
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            object deserialized = binaryFormatter.Deserialize(memoryStream);
 
            return deserialized;
        }
    }
}


Usage

We'll create a class which contains the parameters I'd like to represent, remembering to mark it as serializable:

using System;
 
namespace ObfuscationTesting
{
    [Serializable]
    public class Token
    {
        public string EmailAddress { get; set; }
        public DateTime Expiry { get; set; }
    }
}

To construct the URL with the obfuscated parameters, all we need to have is:

(Assembly reference: System.Web)

using System;
using System.Web;

...
            Token token = new Token()
            {
                EmailAddress = "foo@bar.com",
                Expiry = DateTime.Now.AddHours(1)
            };
            string url = String.Format(@"/sendMessage.aspx?token={0}",
                 HttpUtility.UrlEncode(Obfsucator.Obfuscate(token)));

And, on our web page that receives the URL, we just need to de-obfuscate again:

            string tokenString = Request.QueryString["token"];
            Token token = (Token)Obfsucator.Deobfuscate(tokenString);
            if (token.Expiry < DateTime.Now)
            {
                // Token has expired
                Response.End();
            }
            else
            {
                string toAddress = token.EmailAddress;
                // etc
            }

No comments:

Post a Comment