Cookies are everywhere these days. So much so that here in the European area of the globe, new laws have now been passed requiring web sites to ask visitors if they want to allow them to be used.
Why though, have we gotten to the point where a law is needed to ask for permission to use something so benign? It’s actually quite simple. It is because some companies have been using them in too much of an invasive manner, such as doing things like tracking a whole list of sites you’ve visited and many other similar things.
We all know that the humble cookie was only ever created to store small amounts of data in a visitor’s browser, so that you could do simple things like create an identifier to allow you to know which person was visiting your site and to store simple settings for them.
Unfortunately, because of the wholesale abuse of the cookie, people in general now are refusing to accept cookies more and more, add to this the media scare stories and the result we end up with is a system which can no longer guarantee will return us the data we need to know our visitors.
To developers working in .NET this can be a bit of a problem, because it means you now need to find a new way of making this identity available, and then be able to depend on it to allow you to uniquely identify a web visitor.
Recently one way that developers have been looking to address this problem is via a method known as fingerprinting.
Fingerprinting as the name suggests is a method of generating a unique ID for each visitor, much like how a person has a unique fingerprint.
This ID is generated using data that’s always presented as part of a standard web request using items such as (but not limited to) the following:
- The visitor’s IP address
- The visitor’s browser agent
- The java / flash / Silverlight / other plugin version
and much more.
For the rest of this post, I’m going to show you how to generate a similar fingerprint in C# for use in your own projects.
The fingerprint itself is no more than an MD5 hash of a given string, this string is built dynamically from the data made available to it, in the browser’s request.
In our case we’re going to use the following variables:
- SERVER_PROTOCOL
- HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE
- HTTP_USER_AGENT
- HTTP_ACCEPT
- HTTP_CLIENT_IP
- HTTP_X_FORWARDED_FOR
- REMOTE_ADDR
The last three are all used to hold an IP address of the connecting client; we need to check all three just in case our browser is coming to us via a proxy of some kind.
This means the first thing we need is a method to get our IP address:
private static string GetIpAddress(NameValueCollection availableVariables) { string address = string.Empty; if(!string.IsNullOrEmpty(availableVariables["HTTP_CLIENT_IP"])) { address = availableVariables["HTTP_CLIENT_IP"]; } if (!string.IsNullOrEmpty(availableVariables["HTTP_X_FORWARDED_FOR"])) { address = availableVariables["HTTP_X_FORWARDED_FOR"]; } if (!string.IsNullOrEmpty(availableVariables["REMOTE_ADDR"])) { address = availableVariables["REMOTE_ADDR"]; } if(string.IsNullOrEmpty(address)) { address = "0.0.0.0"; } return address; }
The NameValueCollection we provide to it is the same collection that can be found in:
Request.ServerVariables
The result of this function is either an IPv4 or IPv6 address or ‘0.0.0.0’ if one couldn’t be detected.
To generate the fingerprint itself, we first need to grab the variables we need:
string ipAddress = GetIpAddress(availableVariables); string protocol = !string.IsNullOrEmpty(availableVariables["SERVER_PROTOCOL"]) ? availableVariables["SERVER_PROTOCOL"] : string.Empty; string encoding = !string.IsNullOrEmpty(availableVariables["HTTP_ACCEPT_ENCODING"]) ? availableVariables["HTTP_ACCEPT_ENCODING"] : string.Empty; string language = !string.IsNullOrEmpty(availableVariables["HTTP_ACCEPT_LANGUAGE"]) ? availableVariables["HTTP_ACCEPT_LANGUAGE"] : string.Empty; string userAgent = !string.IsNullOrEmpty(availableVariables["HTTP_USER_AGENT"]) ? availableVariables["HTTP_USER_AGENT"] : string.Empty; string accept = !string.IsNullOrEmpty(availableVariables["HTTP_ACCEPT"]) ? availableVariables["HTTP_ACCEPT"] : string.Empty;
The last thing that then needs to be done is to concatenate all of your parts together, then create and run them through your chosen hashing function.
Note you can use ANY of the http variables you like but be careful about what you choose. You need to make sure that any variables you decide to include have a very low chance of changing.
Above we’ve used the IP address, in reality it’s actually one of the things you’d not want to use considering that most domestic internet connections these days have some form of dynamic IP allocation.
The smallest change to any part of the string will generate a new hash, and depending on what you’re using your fingerprint for that could make a lot of difference.
However, in most cases where I’ve looked at this method, you get a good unique ID that remains true about 75% to 80% of the time, which in general is about the same as the cookie method.
To close this post off, I present to you a full class that can be used as a starting point for your own Fingerprint generation:
using System; using System.Collections.Specialized; using System.Linq; using System.Security.Cryptography; using System.Text; namespace Intranet.WebUi.Classes { public static class Fingerprinter { public static string FingerPrint { get; private set; } private static readonly MD5 _hasher = MD5.Create(); public static void Generate(NameValueCollection availableVariables) { string ipAddress = GetIpAddress(availableVariables); string protocol = !string.IsNullOrEmpty(availableVariables["SERVER_PROTOCOL"]) ? availableVariables["SERVER_PROTOCOL"] : string.Empty; string encoding = !string.IsNullOrEmpty(availableVariables["HTTP_ACCEPT_ENCODING"]) ? availableVariables["HTTP_ACCEPT_ENCODING"] : string.Empty; string language = !string.IsNullOrEmpty(availableVariables["HTTP_ACCEPT_LANGUAGE"]) ? availableVariables["HTTP_ACCEPT_LANGUAGE"] : string.Empty; string userAgent = !string.IsNullOrEmpty(availableVariables["HTTP_USER_AGENT"]) ? availableVariables["HTTP_USER_AGENT"] : string.Empty; string accept = !string.IsNullOrEmpty(availableVariables["HTTP_ACCEPT"]) ? availableVariables["HTTP_ACCEPT"] : string.Empty; string stringToTokenise = ipAddress + protocol + encoding + language + userAgent + accept; FingerPrint = GetMd5Hash(_hasher, stringToTokenise); } private static string GetIpAddress(NameValueCollection availableVariables) { string address = string.Empty; if(!string.IsNullOrEmpty(availableVariables["HTTP_CLIENT_IP"])) { address = availableVariables["HTTP_CLIENT_IP"]; } if (!string.IsNullOrEmpty(availableVariables["HTTP_X_FORWARDED_FOR"])) { address = availableVariables["HTTP_X_FORWARDED_FOR"]; } if (!string.IsNullOrEmpty(availableVariables["REMOTE_ADDR"])) { address = availableVariables["REMOTE_ADDR"]; } if(string.IsNullOrEmpty(address)) { address = "0.0.0.0"; } return address; } private static string GetMd5Hash(MD5 md5Hash, string input) { byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input)); StringBuilder sBuilder = new StringBuilder(); for (int i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } return sBuilder.ToString(); } } }
In some future posts we’ll explore other ideas that may be used in .NET applications and look at other ways we might be able to generate unique ID’s on the web.
In the mean time if you have any ideas, or would like to see a certain part of the .NET stack covered in this column, please feel free to ping me on twitter @shawty_ds and let me know your thoughts.
Shawty