Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Encrypting Cookies to prevent tampering

0.00/5 (No votes)
23 Dec 2004 1  
In this article, we talk about the lack of Cookie security built-into the ASP.NET framework, and a decent workaround to provide integrated tamper proof security for cookie data.

Introduction

Working with cookies in ASP.NET is a trivial task. However, the security of information traveling back and forth is non-existent. Basically, any casual hacker could view and change this plain text cookie. This project addresses this need to possibly protect that cookie data from the occasional hacker. Note that the security implemented here is most likely only as durable as the ASP.NET ViewState's encryption.

I believe their encryption is based on a hash plus the actual data encrypted, so that if you try to change the data, it's pretty difficult. ASP.NET's ViewState uses the Machinekey config file section to configure the keys and such... this is important when the application is going to be run on a web farm, where load balancing webservers may be in no affinity mode.

So this would be tamper-evident security, but when the actual data itself isn't that sensitive, like the user's current ID or something similar. So note that I'm not guaranteeing that the data in the encrypted cookie can't be read, so don't store anything sensitive in there! You've been warned [cue fast lawyer talk].

Working with HttpCookieEncryption

You basically reference the DLL or include the code in your project. The HttpCookieEncryption type was rooted into the System.Web namespace, so no extra "usings" or "Imports" that you don't already have by default in ASP.NET projects.

Simply make a call to HttpCookieEncryption.Encrypt to encrypt the specified cookie. Note that the second overload to Encrypt actually modifies the Response, whereas the first does not.

On the next request, you can decrypt the encrypted cookie by calling HttpCookieEncryption.Decrypt(). This retrieves the specified cookie and returns a new instance with the decrypted value. Neither of the Decrypt methods modify the cookie in the current Response.

void Page_Load(object sender,EventArgs e)
{
    HttpCookie myEncryptedCookie = 
      HttpCookieEncryption.Decrypt(this.Context,"myEncryptedCookie");
    if(myEncryptedCookie==null)
    {
        //cookie didnt exist, probably first request, 

        //this is normally a login redirect or something?

        HttpCookie test = Response.Cookies["myEncryptedCookie"];
        //always returns an instance


        test["key1"]="value1";
        test["key2"]="value2";

        HttpCookieEncryption.Encrypt(this.Context,"myEncryptedCookie"); 
        //updates the Response, so subsequent calls to the cookie's value will 

        //yield the encrypted hex string, so if you lose the reference

        //to the decrypted instance, just call HttpCookieEncryption.Decrypt


        HttpCookie decrypted = HttpCookieEncryption.Decrypt(this.Context, 
                                                    "myEncryptedCookie"); 
        //note that decrypt NEVER updates the Response Cookie in memory.


        //these will be equal

        if(test["key1"].Equals(decrypted["key1"]) && 
                        test["key2"].Equals(decrypted["key2"]))
            //symmetric algorithm magic

        else
            //should never happen.


    }
}

How it works

The most important part is utilizing ASP.NET's built in MachineKey property for encrypting the cookie. This property is used for serializing the ViewState and is also used by FormsAuthentication.Encrypt to encrypt a FormsAuthenticationTicket. The logical choice was to use this key, since it's a standard value that can be synchronized on several web servers in a web farm, allowing a "No affinity" web cluster.

Reflection APIs are used to get a pointer to key methods used internally by the System.Web API. A helper class, called MachineKeyWrapper was created to handle this work, as shown here:

private static MethodInfo _encOrDecData;
private static MethodInfo _hexStringToByteArray;
private static MethodInfo _byteArrayToHexString;

static MachineKeyWrapper()
{
    object config = HttpContext.Current.GetConfig("system.web/machineKey");
    Type configType = config.GetType();

    Type machineKeyType = 
         configType.Assembly.GetType("System.Web.Configuration.MachineKey");
    if (machineKeyType == null)
    {
        // try to get asp.net 2.0 type

        machineKeyType = 
         configType.Assembly.GetType("System.Web.Configuration.MachineKeySection");
    }

    BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Static;

    _encOrDecData = machineKeyType.GetMethod("EncryptOrDecryptData", bf);
    _hexStringToByteArray = machineKeyType.GetMethod("HexStringToByteArray", bf);
    _byteArrayToHexString = machineKeyType.GetMethod("ByteArrayToHexString", bf);

    //is there any way to get some kind of pointer?  or just trust:

    // MethodBase.Invoke

    // RuntimeMethodInfo.Invoke

    // RuntimeMethodHandle.InvokeFast

    // RuntimeMethodHandle._InvokeFast

    // ...lot of extra calls...


    if( _encOrDecData==null || 
        _hexStringToByteArray==null || _byteArrayToHexString==null )
    {
        throw new 
          InvalidOperationException("Unable to get the methods to invoke.");
    }
}

The MachineKeyWrapper then mimics the internal System.Web.MachineKey class:

public static byte[] HexStringToByteArray(string str)
{
    return (byte[]) _hexStringToByteArray.Invoke(null, 
                                new object[] { str });
}
public static string ByteArrayToHexString(byte[] array, int length)
{
    return (string) _byteArrayToHexString.Invoke(null, 
                      new object[] { array, length });
}
public static byte[] EncryptOrDecryptData(bool encrypting, 
           byte[] data, byte[] mod, int index, int length)
{
    return (byte[])_encOrDecData.Invoke(null, 
            new object[] { encrypting, data, mod, index, length });
}

Some caveats

Obviously, since we've used Reflection to obtain handles to the internal MachineKey's methods, they aren't as optimized as being able to hit the "real thing." However, I don't believe this will be a major problem, simply because you should technically only need to decrypt once, and encrypt once, so the overhead of running through Reflection's bindings should be minimal. A recommendation is to hold onto a reference to the decrypted cookie as long as possible, and pass it around explicitly as much as possible.

void Page_Load(object sender,EventArgs e)
{
    HttpCookie userDetails = HttpCookieSecurity.Decrypt(this.Context,"userdetails");

    SomeRoutineUsingCookie(userDetails);
}
void SomeRoutineUsingCookie(HttpCookie userDetails)
{
    this.lblFirstName.Text = userDetails["FirstName"];
    this.lblLastName.Text = userDetails["LastName"];
    this.lblUserId.Text = userDetails["Id"];
}

Where do we go from here?

HttpCookieEncryption is designed to help tamper-proof your cookies. ASP.NET 2.0 introduced DataSource controls and specifically a CookieParameter type that provides data from a cookie to the data source control when data is requested.

My new article about updating CookieParameter builds on the existing CookieParameter object, adding encryption support and multi-valued cookie support.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here