A URL Friendly Hash Algorithm

I always try to use GUIDs to identify public resources on a website:

//www.code-overload.co.uk/private/4B979F21-BAEF-46EE-A1E2-B62C114DCFA7

In order for this to work the GUID needs to be persisted, usually in a database. If you can’t persist it, but you need the public ID to be the same for each user/visit, then you can try hashing the private ID. This will generate a non-guessable, repeatable representation of the private ID. The only problem is that hashing usually results in an array of bytes that are not very URL friendly… What I wanted was this:

//www.code-overload.co.uk/private/IMx3Fa3Ds3w9SP6UvNyoseVSee

Here’s a work-around that takes a SHA1 hash, converts the output to Base64, then strips anything that is not a letter or a number. Note – there is an increased chance of hash-collisions, so you need to test it (see unit test below).

private const string SALT = "PUT SOME RANDOM STRING HERE";

/// <summary>
/// Produces a URL friendly hash based on a private ID.
/// </summary>
/// <param name="privateId">The internal (private) ID of the record you want to protected.</param>
/// <returns></returns>
public static string GetUrlFriendlyHash(int privateId)
{
    var saltedString = privateId.ToString() + SALT;
    var temp = Encoding.ASCII.GetBytes(saltedString);

    using (var sha1 = new SHA1Managed())
    {
        var hash = sha1.ComputeHash(temp);	
        var base64 = Convert.ToBase64String(hash);
		
        var filtered = base64
            .Where(x => Char.IsLetterOrDigit(x))
            .ToArray();

        return new string(filtered);
    }
}

Before using this, it’s a good idea to check you don’t get any hash collisions. Here’s a unit test that checks all results are unique up to 1,000,000.

[TestMethod]
public void GetUrlFriendlyHash_Should_Be_Unique()
{
    // Arrange
    var expected = 1000000;
    var dictionary = new Dictionary<string, int>();

    // Act
    for (int i = 0; i < expected; i++)
    {
        var hash = GetUrlFriendlyHash(i);
        dictionary.Add(hash, i);
    }

    // Assert
    Assert.AreEqual(expected, dictionary.Count);
}
Advertisements
This entry was posted in Tips and Tricks. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s