Serial generation with PHP

My solution

implode( '-', str_split( substr( strtoupper( md5( time() . rand( 1000, 9999 ) ) ), 0, 20 ), 4 ) );

If your application has a connection back to the server it's trivial, just generate random tokens, store them in a database, and require the application to check back with the server before running. However some customers may find this requirement unacceptable. (I personally will never buy any software with an internet activation requirement. I want to buy software, not rent it.)

For keys with genuineness checkability without having to have a connection back to the server:

  1. On the server, generate a random unique token.

  2. Use a public key cryptography scheme (eg. RSA) to sign the token with a private key that is kept secret to the server.

  3. Encode the unique token and the signature together using some binary-to-text scheme (eg. base64, or just using 2-9A-KMNP-Z symbols for safer typability). The encoded combination is your key or serial.

  4. Embed the public key matching the server's private key in each copy of the application, and have the application prompt for a key at install time. It can split the unique token and the signature, and use the public key to verify that the signature is valid for that token.

This approach requires you bundle some crypto libraries with your software, and most algorithms use quite long signatures, for security, which will make your serial numbers pretty tedious to type in. It's OK if you expect users to copy-and-paste the numbers.

For these reasons many software packages use a less-secure key validation scheme where the checking algorithm is wholly built into the client. This might be a hash, eg. the last four hex digits of the serial must match the lowest two bytes of the SHA1 hash of the rest of the serial combined with a ‘secret’ key. However, since the key has to be bundled in the application, it's possible for a hacker to look at the app code and extract the ‘secret’ key, allowing them to write their own key generators.

That's why for some programs you will see ‘keygens’ available: the app used a less-secure checking algo which left enough information in the application to allow the cracker to reproduce the key-making process. For programs with a more secure setup based on public-key crypto or internet activation, you typically see a ‘cracked’ version of the application instead, where the checking code has been altered/removed.

...which kind of demonstrates that whatever you do, you're still not going to be able to enforce honesty. So don't worry too much about it.


If you want to create random serial numbers centrally, just fetching randomly from an array of numbers and letters should be enough. Use mt_rand(0, count($poolArray)) to get a new index every time and just add that character to your serial until you have say 12 of them.

Generating them in random from such a large pool (26 letters + 10 digits) will almost make sure that you don't have duplicates, but you can always just check for existing ones before you store the new one.

If you have 36 possible characters, and you pick 12 randomly from them to make your serials, that's 36*36*36*...*36 = 36^12 = 4738381338321616896 possible strings.

$pool = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$countPool = strlen($pool) - 1;
$totalChars = 12;

$serial = '' ;
for ($i = 0 ; $i < $totalChars ; $i++) {
    $currIndex = mt_rand(0, $countPool) ;
    $currChar = $pool[$currIndex] ;
    $serial .= $currChar ;
}

Distributing them with your code or having your program check for a valid serial is another issue.


Here's our solution. It generates a key with a configurable size/length and optional suffix based on a valid IPv4, Userid (or any meaningful integer), or text string. It also avoids ambiguous characters (i,1,l,0,o,O) in the standard result.

We append the Userid in the license, and can later convert that portion back to a base10 integer and check if it's valid against the user account that is using the license.

$license = generate_license();
// YF6G2-HJQEZ-8JZKY-8C8ZN

$license = generate_license(123456);
// ZJK82N-8GA5AR-ZSPQVX-2N9C

$license = generate_license($_SERVER['REMOTE_ADDR']);
// M9H7FP-996BNB-77Y9KW-ARUP4

$license = generate_license('my text suffix');
// Q98K2F-THAZWG-HJ8R56-MY-TEXT-SUFFIX

We do check uniqueness in the database when created, but using the IP/Userid in conjunction with the randomness, the likelihood of duplicates is virtually zero.

/**
* Generate a License Key.
* Optional Suffix can be an integer or valid IPv4, either of which is converted to Base36 equivalent
* If Suffix is neither Numeric or IPv4, the string itself is appended
*
* @param   string  $suffix Append this to generated Key.
* @return  string
*/
function generate_license($suffix = null) {
    // Default tokens contain no "ambiguous" characters: 1,i,0,o
    if(isset($suffix)){
        // Fewer segments if appending suffix
        $num_segments = 3;
        $segment_chars = 6;
    }else{
        $num_segments = 4;
        $segment_chars = 5;
    }
    $tokens = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $license_string = '';
    // Build Default License String
    for ($i = 0; $i < $num_segments; $i++) {
        $segment = '';
        for ($j = 0; $j < $segment_chars; $j++) {
            $segment .= $tokens[rand(0, strlen($tokens)-1)];
        }
        $license_string .= $segment;
        if ($i < ($num_segments - 1)) {
            $license_string .= '-';
        }
    }
    // If provided, convert Suffix
    if(isset($suffix)){
        if(is_numeric($suffix)) {   // Userid provided
            $license_string .= '-'.strtoupper(base_convert($suffix,10,36));
        }else{
            $long = sprintf("%u\n", ip2long($suffix),true);
            if($suffix === long2ip($long) ) {
                $license_string .= '-'.strtoupper(base_convert($long,10,36));
            }else{
                $license_string .= '-'.strtoupper(str_ireplace(' ','-',$suffix));
            }
        }
    }
    return $license_string;
}