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 "using
s" 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)
{
HttpCookie test = Response.Cookies["myEncryptedCookie"];
test["key1"]="value1";
test["key2"]="value2";
HttpCookieEncryption.Encrypt(this.Context,"myEncryptedCookie");
HttpCookie decrypted = HttpCookieEncryption.Decrypt(this.Context,
"myEncryptedCookie");
if(test["key1"].Equals(decrypted["key1"]) &&
test["key2"].Equals(decrypted["key2"]))
else
}
}
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)
{
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);
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.