13

Throughout my days as a PHP Programmer and a beginner at C# programming I've always wondered of the best way to generate unique serials such as how Microsoft Office and Microsoft operating systems do.

Does anyone have a good guide to how to handle this, Like what are the important factors in generating the unique serial, prevent duplicates etc. small example on how to create / verify them.

Here is the RFC I'm talking about: https://www.rfc-editor.org/rfc/rfc1982.

7 Answers 7

29

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;
}
1
  • 1
    Can you share the verify function?
    – Mike
    Commented Jan 10, 2019 at 11:10
22

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.

2
  • Perfect, Just the type of information i was looking for :), Great read. +1
    – RobertPitt
    Commented Sep 10, 2010 at 21:12
  • 1
    :-/ this is exactly what I said.
    – Bot
    Commented Sep 11, 2010 at 16:50
15

My solution

implode( '-', str_split( substr( strtoupper( md5( time() . rand( 1000, 9999 ) ) ), 0, 20 ), 4 ) );
3
  • This is very elegant! Commented Nov 20, 2017 at 5:32
  • Good! given the Serial can you decode back to the date?
    – Mr.Web
    Commented Jan 1, 2018 at 17:28
  • @Mr.Web No because the md5 hash function is virtually impossible to reverse Commented Oct 25, 2018 at 19:32
1

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.

2
  • I'm sorry, why with count($pool) give me result 101000100100 while if I explicitly assign 35, it works 3UNPWR3GWHQP, and if I assign 36, it goes with unitialized offset
    – Zyoo
    Commented Feb 1, 2013 at 18:46
  • @yolapop you're absolutely correct. I was meaning to write $pool as an array but somehow ended up writing it as a string. Consider it an array and the methodology works. Commented Feb 2, 2013 at 15:03
1

Here's a modified version of GDP's answer, which includes an integrated checksum which can be verified. Note: Although this method adds a layer of validation, it is not highly secure. For a more secure solution, consider using cryptographic signatures.

This is the sample output:

$license = generate_license();
// PKFAT-NGLNM-SQQ7Q-PB4JN-E7OG0

$license = generate_license(123456);
// DBUK2-T7WCP-JH48A-UCC5R-2N9C-3T7PH

$license = generate_license($_SERVER['REMOTE_ADDR']);
// 9ZD49-BRRWK-NBV4J-GJJ5G-196KW24-7WRTJ

$license = generate_license('my text suffix');
// 8DRAN-FRP8A-2KK2F-ASHFT-MY-TEXT-SUFFIX-9LWUO

and here's how you can validate the license key:

$validLicense = '5258U-NRDBN-72XTC-X9QQ4-2KRDA';
$invalidLicense = '5258U-NRDBN-72XTC-X9QQ4-3KRDB';

var_dump(verify_license($validLicense));
// bool(true)

var_dump(verify_license($invalidLicense));
// bool(false)

To enable verification, we can introduce a checksum into the license key. This checksum can be a simple hash of some segments of the license key, appended to the license itself. During verification, we can compute the checksum again from the license key and compare it to the original checksum. This is the code to generate the license key:

/**
 * Generate a License Key with Alphanumeric Checksum of Segment Length.
 *
 * This function generates a license key and appends an alphanumeric checksum
 * whose length matches the segment length for better uniformity.
 *
 * @param   string  $suffix Optional. Append this to the generated key.
 * @return  string  License key with appended checksum.
 */
function generate_license($suffix = null) {
    // Set default number of segments and segment characters
    $num_segments = 4;
    $segment_chars = 5;

    // Tokens used for license generation (ambiguous characters removed)
    $tokens = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';

    // Initialize license string
    $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;

        // Add separator unless at the last segment
        if ($i < ($num_segments - 1)) {
            $license_string .= '-';
        }
    }

    // Handle optional suffix
    if (isset($suffix)) {
        if (is_numeric($suffix)) {
            $license_string .= '-' . strtoupper(base_convert($suffix, 10, 36));
        } else {
            $long = sprintf("%u", ip2long($suffix), true);
            if ($suffix === long2ip($long)) {
                $license_string .= '-' . strtoupper(base_convert($long, 10, 36));
            } else {
                $license_string .= '-' . strtoupper(str_ireplace(' ', '-', $suffix));
            }
        }
    }

    // Generate alphanumeric checksum and append it to the license string
    $checksum = strtoupper(base_convert(md5($license_string), 16, 36));

    // Adjust the length of the checksum to match segment_chars
    $checksum = substr($checksum, 0, $segment_chars);

    $license_string .= '-' . $checksum;

    return $license_string;
}

You can verify the license key using this function:

/**
 * Verify a License Key with Alphanumeric Checksum of Segment Length.
 *
 * This function verifies a license key by checking its alphanumeric checksum
 * whose length matches the segment length.
 *
 * @param   string  $license License key to verify.
 * @return  bool    True if valid, false otherwise.
 */
function verify_license($license) {
    // Split the license key into segments by dash
    $segments = explode('-', $license);

    // Extract the checksum from the last segment
    $checksum = end($segments);

    // Remove checksum to get the base license string
    array_pop($segments);
    $license_base = implode('-', $segments);

    // Compute checksum for the base license string
    $computed_checksum = strtoupper(base_convert(md5($license_base), 16, 36));

    // Adjust the length of the computed checksum to match the original
    $computed_checksum = substr($computed_checksum, 0, strlen($checksum));

    // Verify by comparing the checksums
    return $checksum === $computed_checksum;
}```
0

I'm not absolutely sure what you want, but there is http://www.php.net/manual/en/function.uniqid.php which creates Unique Identifiers.

If you're more interested in how they are created, I'd advice you to take a look at the PHP source to see how uniqid is implemented.

1
  • uniqid is perfectly predictable. Actually it is only a representation of the microtime it was generatated at. It is in no way random.
    – NikiC
    Commented Sep 10, 2010 at 20:26
0

A little more information is needed. Are you wanting to create a serial key like when you purchase a game or are you wanting a serial for a unique user id.

Are the serial numbers needing to store information such as an expiration time/date?

@nikic - all rand's use time as a base for starting the random generator. That is why the DoD uses lava lamps. They have a whole room dedicated to lava lamps and shine lasers on them to randomly create a unique key.

Edited:

Well what you need to take into effect is what method your C# application will use to communicate with your site.

You will need to create a key with php and store it in the database so that the C# can confirm the serial is a correct one.

You then need to figure out if the PHP will return another value so the C# application is aware the key was authenticated or not.

What I have done in the pasted was create a public key and private key. The public key would be given to the user to validate the product. When they validate the product or login to the system the public key would be checked against the database and the return value would be the private key. During all the interactions with my C# program should the user need to check for updates or pull information from my server the private key would be added to the query to the server to confirm the end user was legit.

Another method I have also used is the above but added additional checking to confirm the user wasn't sharing the key. The C# application would obtain the serial number of the processor in the computer and upon registration of the application it would save it to my database. Then if someone else tried to register the product with the same Public key but different serial number of the processor it would throw an error and they would need to contact support. You can do this to allow 5 different machine ID's or how every many you want.

Creating the registration key is very simple as you really only need to create a random string with an offset (such as the users name).

You could also however create a registration key based on a name or company name that someone provides and add that algorithm to your C# program. The downside to that is C# source code can be decompiled easily the algorithm can be found to create a registration code easily without someone actually paying for the product. By adding in a server that does the authentication it is much more difficult for someone to generate their own serial key.

2
  • lets say i have a product, such as a application, this application requires a serial system, i want to generate a serial in PHP that can be checked and validated within C#, what factors do i have to employ into a script to be able to generate and verify A a key regardless of what interpreter.
    – RobertPitt
    Commented Sep 10, 2010 at 20:55
  • I edited my answer to show some examples of what I have done in the pasted.
    – Bot
    Commented Sep 10, 2010 at 21:27

Not the answer you're looking for? Browse other questions tagged or ask your own question.