ASP.NET: Obfuscating the Querystring

Some time ago I answered somebody's post on the MS C# newsgroup. This person was trying to "pass a connection" to another ASP.NET application from a redirect or a link and couldn't figure out how to do it. Of course, you wouldn't be able to pass the connection object itself (nor should you even try) but you could put the connection string to create it on the QueryString, grab it back out in the Page_Load event of the receiving page, and then simply re-create the same connection.

That was the essence of my suggestion - put the connection string on the QueryString.

Shortly thereafter Steve Orr, whose work I respect, responded that putting a connection string on the querystring posed a substantial security risk, which is correct. So what I'm about to offer here is a nice, compact way to encapsulate the QueryString in ASP.NET as an object that makes it much easier to manipulate, along with some nice code that will perform ASCII-to-HEX scrambling on both the names -- and the values -- of everything on the querystring. Actually you probably could never get an unencrypted connection string on the querystring anyway, it contains illegal "=" characters, so you'd have no choice but to mangle it in some way. (Similarly, we cannot use Base64 encoding since a Base64 encoded string ends with one or two equals signs.)

This is not "encryption" - it's only scrambling or "obfuscation" - which is typically all you need to keep your "stuff" away from prying eyes. If you truly need strong encryption, my suggestion is -- don't use the querystring at all; find another way. Of course, you can add to this, for example doing TRIPLE-DES encryption before performing the ASCI-To-HEX, and the reverse for decrypting, if you wanted to do "overkill". But the bottom line is, it's best not to put sensitive data on the querystring at all -- there are other ways to pass the data.
First, I make use of Bobby DeRosa's QueryString class which is shown here virtually unchanged from his original post:

using System.Collections.Specialized ;
using System.Collections;
using System.Web;
using System;
namespace PAB.Utils
{
   // from Bobby DeRosa :
   // http://www.csharper.net/blog/
   // querystring_class_useful_for_querystring_manipulation__appendage__etc.aspx
 
    public class QueryString : NameValueCollection
    {
         private string document;
        public string Document
        {
             get
             {
                  return document;
             }
        }
 
        public QueryString()
        {
         }
 
         public QueryString(NameValueCollection clone) : base(clone)
        {
         }
 
         public static QueryString FromCurrent()
        {
            return FromUrl(HttpContext.Current.Request.Url.AbsoluteUri);    
         }
 
         public static QueryString FromUrl(string url)
        {
            string[] parts = url.Split("?".ToCharArray());
            QueryString qs = new QueryString();
            qs.document = parts[0];
 
             if(parts.Length == 1)
                 return qs;
 
            string[] keys = parts[1].Split("&".ToCharArray());
            foreach(string key in keys)
            {
                string[] part = key.Split("=".ToCharArray());
                 if(part.Length == 1)
                     qs.Add(part[0], "");
                 qs.Add(part[0], part[1]);
             }
 
             return qs;
        }
 
         public void ClearAllExcept(string except)
        {
            ClearAllExcept(new string[] { except });
        }
 
        public void ClearAllExcept(string[] except)
        {
            ArrayList toRemove = new ArrayList();
            foreach(string s in this.AllKeys)
            {
                foreach(string e in except)
                 {
                     if(s.ToLower() == e.ToLower())
                          if(!toRemove.Contains(s))
                              toRemove.Add(s);
                }
            }
 
            foreach(string s in toRemove)
                 this.Remove(s);
        }
 
        public override void Add(string name, string value)
        {
            if(this[name] != null)
                 this[name] = value;
             else
                 base.Add(name, value);
        }
 
        public override string ToString()
        {
            return ToString(false);
        }
 
         public string ToString(bool includeUrl)
        {
            string[] parts = new string[this.Count];
            string[] keys = this.AllKeys;
             for(int i = 0; i < keys.Length; i++)
                parts[i] = keys[i] + "=" + HttpContext.Current.Server.UrlEncode(this[keys[i]]);
            string url = String.Join("&", parts);
             if((url!=null || url!=String.Empty) && !url.StartsWith("?"))
                url = "?" + url;
             if(includeUrl)
                url = this.document + url;      
             return url;
        }
    }
}

What the above gives us is an easy way to work with the Querystring as a true "object" rather than having to write what I like to refer to as "BS code" just to get items and so on. For example, with a real object encapsulating the Querystring, we can write code like this:

private void btnGo_Click(object sender, System.EventArgs e)
        {
            QueryString  qs = new QueryString();
             qs.Add(this.txtName1.Text,this.txtValue1.Text);
             qs.Add(this.txtName2.Text,this.txtValue2.Text);
             qs.Add(this.txtName3.Text,this.txtValue3.Text);
            string strRedirect= "WebForm2.aspx";
            QueryString qsEncrypted = PAB.Utils.Encryption.EncryptQueryString(qs);
             strRedirect+=qsEncrypted.ToString();
            Response.Redirect(strRedirect,true);        
        }

Tell me that isn't a lot easier to work with! You can see the call the encryption utility passes an instance of the entire Querystring object, and receives back a new one, encrypted - both names and values. All we need to do is call the ToString() method and add it to the base redirect URL. Now, on the receiving end:

private void Page_Load(object sender, System.EventArgs e)
        {
            QueryString qs = QueryString.FromCurrent();            
 txtRaw.Text+=((QueryString)PAB.Utils.Encryption.DecryptQueryString(qs)).ToString();
        }


Finally, here's the Encryption Utility class that works with this, with the Hex routines I finally settled on:

using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Security.Cryptography;
using System.Globalization ;
using System.Web;
 
namespace PAB.Utils
{
    public class Encryption
    {    
 
        private Encryption()
        {
        }
 
        public static QueryString EncryptQueryString(QueryString queryString)
        {            
            QueryString newQueryString = new QueryString();
            string nm=String.Empty;
            string val=String.Empty;
            foreach(string name in queryString)
            {
                nm= name;
                 val=queryString[name];                 
   newQueryString.Add(PAB.Utils.Encryption.Hex(nm), PAB.Utils.Encryption.Hex(val));
             }
             return newQueryString;
        }
 
        public static QueryString DecryptQueryString(QueryString queryString)
        {        
            QueryString newQueryString = new QueryString();
            string nm;
            string val;
            foreach(string name in queryString)
             {
                 nm=PAB.Utils.Encryption.DeHex(name);
                 val=PAB.Utils.Encryption.DeHex(queryString[name]);                  
                 newQueryString.Add(nm, val );
             }
                 return newQueryString;
        }
 
        public static string DeHex(string hexstring)
        {
            string ret = String.Empty;
            StringBuilder sb = new StringBuilder(hexstring.Length /2);
             for(int i=0;i<= hexstring.Length-1;i=i+2)
             {
 sb.Append((char)int.Parse(hexstring.Substring(i,2), NumberStyles.HexNumber));                }
             return sb.ToString();
        }
 
    public static string Hex(string sData)
        {
            string temp = String.Empty;;
            string newdata=String.Empty;
            StringBuilder sb = new StringBuilder(sData.Length *2);
             for (int i = 0;i < sData.Length;i++)
             {
                 if ((sData.Length - (i + 1)) > 0)
                {
                    temp = sData.Substring(i,2);
                     if (temp == @"\n") newdata += "0A";
                     else if (temp == @"\b") newdata += "20";
                     else if (temp == @"\r") newdata += "0D";
                     else if (temp == @"\c") newdata += "2C";
                     else if (temp == @"\\") newdata += "5C";
                     else if (temp == @"\0") newdata += "00";
                     else if (temp == @"\t") newdata += "07";
                     else
                     {
    sb.Append(   String.Format("{0:X2}", (int)(sData.ToCharArray())[i]));
                          i--;
                     }
                 }
                 else
                 {
   sb.Append(  String.Format("{0:X2}", (int)(sData.ToCharArray())[i]));
                 }
                 i++;
            }
            return sb.ToString();
        }
    }
}

I experimented with XOR, ROT13, ROT39 and ASCII-to-Hex, which is the method I finally settled on. Since this method doesn't require any UrlEncoding normalization, that part of the QueryString class has been commented out.  The net result of all this is that this QueryString (which would not even pass as it contains illegal characters):

WebForm2.aspx?connstring=server=xyz;database=yoda;uid=jocko;pwd=youbetcha;

would now look like so:

WebForm2.aspx?636F6E6E737472696E67=7365727665723D78797A3B646174616261
73653D796F64613B7569643D6A6F636B6F3B7077643D796F756265746368613B

You can see the two static methods EncryptQuerystring and DecryptQuerystring which accept and return Querystring objects. So now, if you have a need to scramble your QueryStrings for "basic security", this becomes an easy way to handle it. A nice side benefit of this is that now you can put whatever you want into a querystring item - spaces, semicolons, slashes, equals signs -- you name it, and it will be faithfully reproduced on the receiving end.

N.B. Thanks to commenters Steve and Richard on the original of this article for their coding suggestions, both of which have been incorporated into the downloadable source code below. And - If you truly want secure transmission of items from one page to another, DO NOT put them on the querystring!

Download the Visual Studio 2010 Solution.

By Peter Bromberg   Popularity  (7446 Views)