Note:
This solution is on the assumption you want your licence key to always be in fixed format
(see below) and still self authenticated
FORMAT : XXXXX-XXXXX-XXXXX-XXXXX-XXXX
If that is not the case refer to @ircmaxell
for a better solution
Introduction
Self authenticated serial is tricky solution because:
- Limited Size of Serial
- It need to authenticate it self without Database or any storage
- If private key is leaked .. it can easily be reversed
Example
$option = new CheckProfile();
$option->name = "My Application"; // Application Name
$option->version = 0.9; // Application Version
$option->username = "Benedict Lewis"; // you can limit the key to per user
$option->uniqid = null; // add if any
$checksum = new Checksum($option);
$key = $checksum->generate();
var_dump($key, $checksum->check($key));
Output
string '40B93-C7FD6-AB5E6-364E2-3B96F' (length=29)
boolean true
Please note that any modification in the Options would change the key and make it invalid;
Checking for collision
I just ran this simple test
set_time_limit(0);
$checksum = new Checksum($option);
$cache = array();
$collision = $error = 0;
for($i = 0; $i < 100000; $i ++) {
$key = $checksum->generate();
isset($cache[$key]) and $collision ++;
$checksum->check($key) or $error ++;
$cache[$key] = true;
}
printf("Fond %d collision , %d Errors in 100000 entries", $collision, $error);
Output
Fond 0 collision , 0 Errors in 100000 entries
Better Security
By default the script uses sha1
but PHP
has a lot of better hash functions
you can get that with the following code
print_r(hash_algos());
Example
$checksum = new Checksum($option, null, "sha512");
Class Used
class Checksum {
// Used used binaray in Hex format
private $privateKey = "ec340029d65c7125783d8a8b27b77c8a0fcdc6ff23cf04b576063fd9d1273257"; // default
private $keySize = 32;
private $profile;
private $hash = "sha1";
function __construct($option, $key = null, $hash = "sha1") {
$this->profile = $option;
$this->hash = $hash;
// Use Default Binary Key or generate yours
$this->privateKey = ($key === null) ? pack('H*', $this->privateKey) : $key;
$this->keySize = strlen($this->privateKey);
}
private function randString($length) {
$r = 0;
switch (true) {
case function_exists("openssl_random_pseudo_bytes") :
$r = bin2hex(openssl_random_pseudo_bytes($length));
break;
case function_exists("mcrypt_create_ivc") :
default :
$r = bin2hex(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM));
break;
}
return strtoupper(substr($r, 0, $length));
}
public function generate($keys = false) {
// 10 ramdom char
$keys = $keys ? : $this->randString(10);
$keys = strrev($keys); // reverse string
// Add keys to options
$this->profile->keys = $keys;
// Serialise to convert to string
$data = json_encode($this->profile);
// Simple Random Chr authentication
$hash = hash_hmac($this->hash, $data, $this->privateKey);
$hash = str_split($hash);
$step = floor(count($hash) / 15);
$i = 0;
$key = array();
foreach ( array_chunk(str_split($keys), 2) as $v ) {
$i = $step + $i;
$key[] = sprintf("%s%s%s%s%s", $hash[$i ++], $v[1], $hash[$i ++], $v[0], $hash[$i ++]);
$i ++; // increment position
}
return strtoupper(implode("-", $key));
}
public function check($key) {
$key = trim($key);
if (strlen($key) != 29) {
return false;
}
// Exatact ramdom keys
$keys = implode(array_map(function ($v) {
return $v[3] . $v[1];
}, array_map("str_split", explode("-", $key))));
$keys = strrev($keys); // very important
return $key === $this->generate($keys);
}
}