Thursday, March 15, 2012

Query String Encryption with support for Post Back in ASP.Net

Query strings are often used to carry information in webpages. We may want to pass the selected id to the next page and we usually using the query string for this purpose.
for e.g http://www.abc.com?user=najeed&productid=102
This shows the product id to the visitors and hackers can do a lot with these values.
It would be nice if we could encrypt the entire query string so it wouldn’t carry any readable information. Our aim is to show the query string as encrypted values as shown below even after the post back in the page.
http://www.abc.com?enc=DucsweegFcH9DLNrhVHuvexZi5GIGwiG
ASP.Net gives us a solution for this, and we can do the query string encryption with the help of an HttpModule.
We have to do the following steps to achieve this (I am creating all the .cs files mentioned below in App_Code folder to avoid the confusion about the Namespaces. You can also create these files anywhere in the project or even in a separate class library, but need to add the proper reference or fully qualified class name)
1. Create HttpModule to capture all the HttpRequests and encrypt/decrypt the query string
2. Create a class to Encrypt and Decrypt
3. Add Custom Control Adapter to force for the Custom control rendering
4. Modify the Web.config and browser file to use the HttpModule and Control Adapter during the Page/Control processing

1. Create HttpModule to encrypt/decrypt the query string
public class QueryStringModule : IHttpModule
{
private const string ENCRYPTED_PARAMETER_NAME = "enc=";
public const string ENCRYPTION_KEY = "dotnet";
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = application.Context;
string rawUrl = context.Request.RawUrl;
if (context.Request.Url.OriginalString.Contains("aspx")
&& rawUrl.Contains("?"))
{
string query = UIHelpers.ExtractQueryString(rawUrl);
string path = UIHelpers.GetVirtualPath(rawUrl);
if (query.StartsWith(ENCRYPTED_PARAMETER_NAME, StringComparison.OrdinalIgnoreCase))
{
string decryptedQuery = QueryStringModule.Decrypt(query.Replace(ENCRYPTED_PARAMETER_NAME, string.Empty));
context.RewritePath(path, String.Empty, decryptedQuery);
}
else if (context.Request.HttpMethod == "GET")
{
context.Response.Redirect(path + "?" + ENCRYPTED_PARAMETER_NAME + QueryStringModule.Encrypt(query));
}
}
}
public static string Decrypt(string stringToDecrypt)
{
return new Encryptor().Decrypt(stringToDecrypt, QueryStringModule.ENCRYPTION_KEY);
}
public static string Encrypt(string stringToDecrypt)
{
return ENCRYPTED_PARAMETER_NAME + new Encryptor().Encrypt(stringToDecrypt, QueryStringModule.ENCRYPTION_KEY);
}
}

This class uses a UIHelper and it can be defined as follows
public class UIHelpers
{
public static string GetVirtualPath(string rawUrl)
{
rawUrl = rawUrl.Substring(0, rawUrl.IndexOf("?"));
rawUrl = rawUrl.Substring(rawUrl.LastIndexOf("/") + 1);
return rawUrl;
}
public static string ExtractQueryString(string url)
{
int index = url.IndexOf("?") + 1;
return url.Substring(index);
}
}

2. Create a class to Encrypt and Decrypt
This class uses the DESCryptoServiceProvider class in the .Net framework. You can use your own encryption techniques inside this class.
public class Encryptor
{
private byte[] key = { };
private byte[] IV = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef };
public string Decrypt(string stringToDecrypt, string SEncryptionKey)
{
byte[] inputByteArray = new byte[stringToDecrypt.Length + 1];
try
{
key = System.Text.Encoding.UTF8.GetBytes(NormalizeEncryptionKEy(SEncryptionKey));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
inputByteArray = Convert.FromBase64String(stringToDecrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
catch (Exception e) { return e.Message; }
}

public string Encrypt(string stringToEncrypt, string SEncryptionKey)
{
try
{
key = System.Text.Encoding.UTF8.GetBytes(NormalizeEncryptionKEy(SEncryptionKey));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputByteArray = System.Text.Encoding.UTF8.GetBytes(stringToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(key, IV), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
catch (Exception e){ return e.Message; }
}
private string NormalizeEncryptionKEy(string SEncryptionKey)
{
if (String.IsNullOrEmpty(SEncryptionKey))
{
throw new ArgumentNullException("Null Key not allowed");
}
SEncryptionKey = SEncryptionKey.PadRight(8, 'Q');
return SEncryptionKey;
}
}

3. Add Custom Control Adapter
public class FormRewriterAdapter : System.Web.UI.Adapters.ControlAdapter
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
base.Render(new HtmlTextReWriter(writer));
}
}
public class HtmlTextReWriter : System.Web.UI.HtmlTextWriter
{
private const string WRITTEN_ACTION_KEY = "";
public HtmlTextReWriter(System.Web.UI.HtmlTextWriter writer)
: base(writer)
{
this.InnerWriter = writer.InnerWriter;
}
public override void WriteAttribute(string name, string value, bool encode)
{
if (name == "action")
{
HttpContext Context = HttpContext.Current;
if (Context.Items[WRITTEN_ACTION_KEY] == null)
{
value = Context.Request.RawUrl;
Context.Items[WRITTEN_ACTION_KEY] = true;
}
}
base.WriteAttribute(name, value, encode);
}
}

4. Modify the Web.config and browser file
Add a new browser file say form.browser to the App_Browser folder and modify the contents as below
<code style="color:#000000;word-wrap:normal;">
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm" adapterType="FormRewriterAdapter" />
</controlAdapters>
</browser>
</browsers>

Register the new HttpModule QueryStringModule in the web.config under the section system.web
<httpModules>
<add name="QueryStringModule" type="QueryStringModule" />
</httpModules>

Usage
Following are the examples of different scenarios of using QueryString encryption

In aspx page
<a href='QSPage.aspx?<%= QueryStringModule.Encrypt("par1=val1&par2=val2") %>'>Link</a>
In .cs file
Response.Redirect("QSPage.aspx?" + QueryStringModule.Encrypt("par1=val1&par2=val2"));