Click here to Skip to main content
Click here to Skip to main content

Encrypting Cookies to prevent tampering

, 23 Dec 2004
Rate this:
Please Sign up or sign in to vote.
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

About the Author

Eric Newton
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
QuestionLicense question Pinmemberpaul717-Apr-09 4:24 
GeneralCookie Security for .NET 2 PinmemberAdam Tibi3-Apr-06 9:20 
GeneralASP.NET 2.0 update PinmemberAdam Tibi7-Feb-06 5:54 
QuestionEncrypted Cookie doesn't look as expected PinmemberAdam Tibi7-Feb-06 5:50 
QuestionHttpCookieSecurity? Pinmemberthekubrix10-Oct-05 6:12 
GeneralAmbiguous match found PinmemberBret Williams9-Aug-05 10:07 
GeneralRe: Ambiguous match found PinmemberPaigeC7213-Sep-05 8:13 
GeneralRe: Ambiguous match found PinmemberEric Newton8-Oct-05 4:34 
GeneralRe: Ambiguous match found PinmemberUmer Khan25-Apr-07 21:37 
GeneralProblems with 1 solution and many projects Pinmembercesaresparza9-Feb-05 15:18 
Hi Eric..
 
I´m working with 1 solution and 3 projects.
Something like this.
 
Solution 1
Project1 ----> Start up project : menu, web.config file and links to project2 and project 3
Project2 --->some aspx, class, function
Project3 --->some aspx, class, function
 
my web.config file is in project1 and i need to encrypt a cookie used in project 3, i add the reference HttpCookieEncryption in project3, but i thing it don't find web.config file in project 3 and an error ocurrs.
 
If i add the reference in project1 i can't use it in project2 or project3 because project2 and project3 are dependencies of project1.
 
Thks Eric. i hope i can explain me clear.. my english isn't pretty good. jeje WTF | :WTF:
GeneralRe: Problems with 1 solution and many projects PinmemberEric Newton10-Feb-05 5:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 23 Dec 2004
Article Copyright 2004 by Eric Newton
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid