Generating a Software License Key String with PHP

I was working on a project recently that required unique API keys to be generated for clients connecting to the server. For various reasons, I settled on the style of license key you commonly see for software packages. You know, the kind you always had to read off the back of a CD case and type in when installing the application. Like H8OV7-HNTB5-JLLOH-W8FG2.

It’s fairly easy to write such a function. The basic idea is to loop around four times—once for each segment—and have a nested loop that runs five times, picking a random character each time. Here’s what I came up with:

function generate_key_string() {

	$tokens = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
	$segment_chars = 5;
	$num_segments = 4;
	$key_string = '';

	for ($i = 0; $i < $num_segments; $i++) {

		$segment = '';

		for ($j = 0; $j < $segment_chars; $j++) {
    			$segment .= $tokens[rand(0, 35)];
		}

		$key_string .= $segment;

		if ($i < ($num_segments - 1)) {
    			$key_string .= '-';
		}

	}

	return $key_string;

}

The $tokens string contains the characters that are valid in the key, so the loop can pick from it. The $segment_chars and $num_segments variables are the number of characters in a segment and the number of segments in the key, respectively. $key_string is an empty string that the loop will add the characters into.

The first for loop runs four times, assuming the desired result is four segments in the key. The inner loop picks a character out of $tokens at random each time it goes around. (PHP strings are also arrays, with the each character having its own numerical offset.) The characters are tacked onto the $segment string.

Then the segment is joined with the $key_string, and a dash character is applied if the loop isn’t on the final segment yet. End result: something like H8OV7-HNTB5-JLLOH-W8FG2.

Now how can you make sure the key is unique when it’s generated?

do {
	$key_string = generate_key_string();
	$result = $db->query("SELECT license_key FROM my_license_key_table WHERE license_key = '$key_string'");
	//$db is a PDO object. If you're not familiar with PDO, check out http://www.webmaster-source.com/2011/08/05/getting-your-feet-wet-with-pdo-and-migrating-old-mysql-code/
} while ($result != false);

You generate a new key string with the function, check to see if it exists in your database, and lather/rinse/repeat until that is no longer the case. Usually you won’t have collisions too often, so it will only need to run once. I’m too lazy to figure out the probability, but considering there are 52,521,875 possible combinations for one 5-character segment…you’re probably not going to run into performance issues anytime soon. And if you do, just add another segment onto your key strings.

  • http://www.trailtrotterpress.com Blaine Moore

    Another way you could improve it is to use the last 5 characters as a checksum for the rest of the string (potentially with a salt) if there might ever be a need for validating the API key w/o a net connection to the server.

  • http://www.deluxeblogtips.com Tran Ngoc Tuan Anh

    While your method guarantees that the key is random, it doens’t guarantee that 2 users have the same key (uniqueness). Why don’t you use something simple like crypt() function. It’s fast, simple and strong enough.

    • http://www.webmaster-source.com redwall_hp

      The key is guaranteed unique by way of the second code block. Before storing the key in the database, a while loop ensures that the key hasn’t already been assigned to a record.

      Crypt() is another solution, possibly a better solution, but sometimes you want to make sure that the string is easy to read out loud or type manually.

  • Rohan

    Thank you very much

  • http://www.binarytides.com/ Silver Moon

    $segment .= $tokens[ rand(0, strlen($tokens)-1) ];

  • https://plus.google.com/+SajjadHossainsajjadhossainhira Sajjad Hossain

    I have created one like this

    $license = rand(1000,9999) . '-' . rand(1000,9999) . '-' . rand(1000,9999) . '-' . rand(1000,9999) . '-' . rand(1000,9999);

    I have already using in my project to generate and validate purchased user. Here is details