Click here to Skip to main content
15,867,453 members
Articles / Web Development / ASP.NET
Article

Encrypting Cookies to prevent tampering

Rate me:
Please Sign up or sign in to vote.
4.80/5 (13 votes)
23 Dec 20043 min read 143.8K   1.7K   68   11
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.

C#
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:

C#
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:

C#
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.

C#
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


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionLicense question Pin
paul717-Apr-09 4:24
paul717-Apr-09 4:24 
GeneralCookie Security for .NET 2 Pin
Adam Tibi3-Apr-06 9:20
professionalAdam Tibi3-Apr-06 9:20 
GeneralASP.NET 2.0 update Pin
Adam Tibi7-Feb-06 5:54
professionalAdam Tibi7-Feb-06 5:54 
QuestionEncrypted Cookie doesn't look as expected Pin
Adam Tibi7-Feb-06 5:50
professionalAdam Tibi7-Feb-06 5:50 
QuestionHttpCookieSecurity? Pin
thekubrix10-Oct-05 6:12
thekubrix10-Oct-05 6:12 
GeneralAmbiguous match found Pin
Bret Williams9-Aug-05 10:07
Bret Williams9-Aug-05 10:07 
When my code hits the static constructor in MachineKeyWrapper, it blows chunks on this line:

_encOrDecData = machineKeyType.GetMethod("EncryptOrDecryptData", bf);

Giving this error:

Ambiguous match found.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Reflection.AmbiguousMatchException: Ambiguous match found.

Any ideas what's wrong?
GeneralRe: Ambiguous match found Pin
PaigeC7213-Sep-05 8:13
PaigeC7213-Sep-05 8:13 
GeneralRe: Ambiguous match found Pin
Eric Newton8-Oct-05 4:34
Eric Newton8-Oct-05 4:34 
GeneralRe: Ambiguous match found Pin
Umer Khan25-Apr-07 21:37
Umer Khan25-Apr-07 21:37 
GeneralProblems with 1 solution and many projects Pin
cesaresparza9-Feb-05 15:18
cesaresparza9-Feb-05 15:18 
GeneralRe: Problems with 1 solution and many projects Pin
Eric Newton10-Feb-05 5:29
Eric Newton10-Feb-05 5:29 

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

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