Shareware license key

2007-09-21

    This article will describe how I have implemented license keys in jsiPodFetch. I hope it will help someone else trying to do this. It would also be nice if someone reading this gave me feedback if this solution doesn’t work in practice since I haven’t analyzed this solution thoroughly enough to swear by it, but I think it will work.

    Through out this code there are places that are open for variation. Most important if you reuse my code here is that you use your own unique strings.

    Most payment processors (SWReg, Plimus, and so on) will be able to query a URL of your choice to create a license key. This solution uses a PHP-script to create a license key that can be validated by code written in any .NET-language. I will use C# for my examples.

    The solution uses a multi part license key so that you can change the application code that checks the license when/if it gets cracked or keygened.

    Let’s start with the PHP-code that creates the license key.

    I start by creating three unique strings. Let’s use ‘AAA’, ‘BBB’, ‘CCC’. (I use more than three, but it will be enough to make my point.) $g1 = 'AAA'; $g2 = 'BBB'; $g3 = 'CCC';

    …then I create a hash from the unique string concatenated with the e-mail address used for registering the application. By the way; I am constantly truncating the values at five characters to make the key a bit smaller. It will make the hash values weaker, but I think it will OK anyway. $key1 = substr(md5($g1 . $_GET["email"]), 1, 5); $key2 = substr(md5($g2 . $_GET["email"]), 1, 5); $key3 = substr(md5($g3 . $_GET["email"]), 1, 5);

    … and that’s about it. Now I can concatenate the individual hashes and the e-mail address. $license = $_GET["email"] . "-" . $key1 . "-" . $key2 . "-" . $key3;

    …then I create a hash of the license and concatenate that with the license. $hash = substr(md5($license), 1, 5); $license = $license . "-" . $hash;

    Finally I return the license key: echo ($license);

    The result is this: foo@foo.com-fb821-90d34-8b8c6-ca6ab

    Now it’s time for some C# code. The main obstacle was getting a PHP compatible hash value out of strings in .NET. I googled a bit and found this (I’m not sure where it came from though so I can’t give proper credit.): public static string Md5Hash (string pass) { MD5 md5 = MD5CryptoServiceProvider.Create (); byte[] dataMd5 = md5.ComputeHash (Encoding.Default.GetBytes (pass)); StringBuilder sb = new StringBuilder(); for (int i = 0; i < dataMd5.Length; i++) sb.AppendFormat("{0:x2}", dataMd5[i]); return sb.ToString().Substring(1, 5); } This method will return the same string as the PHP md5() method given the same input.

    Since I hashed all concatenated sub keys I can now validate a given license key with this: public static bool LicenseIsValid(string license) { int lastHyphen = license.LastIndexOf("-"); if(lastHyphen < 4 ) { return false; } string l = license.Substring(0, lastHyphen); string hash = Md5Hash(l); string[] lines = license.Split('-'); return (hash == lines[lines.Length -1]); }

    I could include a fourth secret string when I do the hash of the full key, but at this stage I only want to validate that it is a license key for my program. I don’t want to check its authenticity yet.

    To authenticate a key I get one of the hashed sub keys. With this function: public static string GetKey(int n, string license) { string[] lines = license.Split('-'); return lines[n]; }

    I then hash the secret string I used for the n’th value together with the email and compare the result with the sub key from the license.

    string email = GetKey(0); string validHash = Md5Hash ("AAA" + email); if(validHash == GetKey(1)) { //Allow registered stuff } else { //Inform user that thy have to pay. }

    To make it a little bit harder to crack this, make sure to write this code in several places. If you encapsulate it in a method there is only one place to bypass.

    When someone has created valid keys you can recompile the application and use your second secret key so that the key maker has to restart his work. You could also move the checking code around a bit so the cracker has to redo his work.

    That’s all there is to it.

    One thing I miss with this solution is the ability to encode data, like a date, into the key.