Check if a generated license is valid

Benedict Lewis picture Benedict Lewis · Apr 25, 2013 · Viewed 11k times · Source

I have a PHP script that generates some strings which will be used as license keys:

function KeyGen(){
     $key = md5(microtime());
     $new_key = '';
     for($i=1; $i <= 25; $i ++ ){
               $new_key .= $key[$i];
               if ( $i%5==0 && $i != 25) $new_key.='-';
     }
  return strtoupper($new_key);
  }
$x = 0;
while($x <= 10) {
  echo KeyGen();
  echo "<br />";
$x++; 
}

After running the script once, I got these:

8B041-EC7D2-0B9E3-09846-E8C71
C8D82-514B9-068BC-8BF80-05061
A18A3-E05E5-7DED7-D09ED-298C4
FB1EC-C9844-B9B20-ADE2F-0858F
E9AED-945C8-4BAAA-6938D-713ED
4D284-C5A3B-734DF-09BD6-6A34C
EF534-3BAE4-860B5-D3260-1CEF8
D84DB-B8C72-5BDEE-1B4FE-24E90
93AF2-80813-CD66E-E7A5E-BF0AE
C3397-93AA3-6239C-28D9F-7A582
D83B8-697C6-58CD1-56F1F-58180

What I now am trying to do is change it so that I have another function that will check if the key has been generated using my script. Currently, what I am thinking is setting the $key to the MD5 of one specific string (for example, test) but, of course, that returns all the strings the same.

Can anyone help?

Answer

ircmaxell picture ircmaxell · Apr 25, 2013

There are three basic ways of handling this. How you do it will depend on how many keys you're generating, and how important is may be to be able to invalidate keys at a later day. Which you choose is up to you.

Option 1: Server Database Storage

When the server generates a key (like using your algorithm), you store it in a database. Then later all you need to do to check the key is see if it's in the database.

Note that your algorithm needs a lot more entropy than you're providing it. The current timestamp is NOT enough. Instead, use strong randomness:

$key = mcrypt_create_iv($length_needed, MCRYPT_DEV_URANDOM);

Or, if you don't have mcrypt:

$key = openssl_random_pseudo_bytes($length_needed);

Or if you don't have mcrypt and openssl, use a library

Note that md5 returns a hex output (a-f0-9), where all of the above return full random binary strings (characters 0 - 255). So either base64_encode() it, or bin2hex() it.

Pros:

  • Simple to implement
  • Can "deactive" issued keys at a later date
  • Impossible to forge a new key

Cons:

  • Requires persistent storage per key
  • May not scale that well
  • Requires "key server" to validate keys

Option 2: Signing Keys

Basically, you generate a strong random key (from here out called the private key), and store it on your server. Then, when generating the license key, you generate a random blob, and then HMAC sign it with the private key, and make the license part of that block. That way, you don't need to store each individual key.

function create_key($private_key) {
    $rand = mcrypt_create_iv(10, MCRYPT_DEV_URANDOM);
    $signature = substr(hash_hmac('sha256', $rand, $private_key, true), 0, 10);
    $license = base64_encode($rand . $signature);
    return $license;
}

function check_key($license, $private_key) {
    $tmp = base64_decode($license);
    $rand = substr($tmp, 0, 10);
    $signature = substr($tmp, 10);
    $test = substr(hash_hmac('sha256', $rand, $private_key, true), 0, 10);
    return $test === $signature;
}
    

Pros:

  • Simple to implement
  • Does not require persistent storage
  • Trivial to scale

Cons:

  • Cannot "Deactivate" keys individual
  • Requires storing "private keys"
  • Requires "key server" to validate keys.

Option 3: Public Key Crypto

Basically, you generate a public/private key pair. You embed the public key in your application. Then, you generate a key (similar to "signing keys" above), but instead of signing it with the HMAC signature, you sign it with a private key.

That way, the application (which has the public key) can verify the signature directly without needing to call back to your server.

function create_key($private_key) {
    $rand = mcrypt_create_iv(10, MCRYPT_DEV_URANDOM);

    $pkeyid = openssl_get_privatekey($private_key);
    openssl_sign($rand, $signature, $pkeyid);
    openssl_free_key($pkeyid);

    $license = base64_encode($rand . $signature);
    return $license;
}

function check_key($license, $public_key) {
    $tmp = base64_decode($license);
    $rand = substr($tmp, 0, 10);
    $signature = substr($tmp, 10);

    $pubkeyid = openssl_get_publickey($public_key);
    $ok = openssl_verify($rand, $signature, $pubkeyid);
    openssl_free_key($pubkeyid);

    return $ok === 1;
}

Pros:

  • Simple to implement
  • Does not require persistent storage
  • Trivial to scale
  • Does not require "key server" to validate keys

Cons:

  • Cannot "Deactivate" keys individual
  • Requires storing "private keys"