1
\$\begingroup\$

I've made this for generating license keys & later checking if they're OK and valid.

A key is generated per-user and has an expiration time. That works fine.

Could anyone please check if it looks secure - as in, hard to crack without tampering with the verification code?

class LicenseException extends Exception{}


/**
 * Fix non-ascii characters and normalize name
 */
function sanitizeName($name)
{
    $translit = Array(
        'ä' => 'a', 'Ä' => 'A', 'á' => 'a', 'Á' => 'A', 'à' => 'a', 'À' => 'A',
        'ã' => 'a', 'Ã' => 'A', 'â' => 'a', 'Â' => 'A', 'č' => 'c', 'Č' => 'C',
        'ć' => 'c', 'Ć' => 'C', 'ď' => 'd', 'Ď' => 'D', 'ě' => 'e', 'Ě' => 'E',
        'é' => 'e', 'É' => 'E', 'ë' => 'e', 'Ë' => 'E', 'è' => 'e', 'È' => 'E',
        'ê' => 'e', 'Ê' => 'E', 'í' => 'i', 'Í' => 'I', 'ï' => 'i', 'Ï' => 'I',
        'ì' => 'i', 'Ì' => 'I', 'î' => 'i', 'Î' => 'I', 'ľ' => 'l', 'Ľ' => 'L',
        'ĺ' => 'l', 'Ĺ' => 'L', 'ń' => 'n', 'Ń' => 'N', 'ň' => 'n', 'Ň' => 'N',
        'ñ' => 'n', 'Ñ' => 'N', 'ó' => 'o', 'Ó' => 'O', 'ö' => 'o', 'Ö' => 'O',
        'ô' => 'o', 'Ô' => 'O', 'ò' => 'o', 'Ò' => 'O', 'õ' => 'o', 'Õ' => 'O',
        'ő' => 'o', 'Ő' => 'O', 'ř' => 'r', 'Ř' => 'R', 'ŕ' => 'r', 'Ŕ' => 'R',
        'š' => 's', 'Š' => 'S', 'ś' => 's', 'Ś' => 'S', 'ť' => 't', 'Ť' => 'T',
        'ú' => 'u', 'Ú' => 'U', 'ů' => 'u', 'Ů' => 'U', 'ü' => 'u', 'Ü' => 'U',
        'ù' => 'u', 'Ù' => 'U', 'ũ' => 'u', 'Ũ' => 'U', 'û' => 'u', 'Û' => 'U',
        'ý' => 'y', 'Ý' => 'Y', 'ž' => 'z', 'Ž' => 'Z', 'ź' => 'z', 'Ź' => 'Z'
    );

    return ucwords(
        strtolower(
            strtr(
                trim($name),
                $translit
            )
        )
    );
}


/**
 * Generate a license key for user's real name and expiration timestamp.
 *
 * $name ... real user's name
 * $expires ... unix timestamp when the key expires
 */
function generate_license_key($name, $expires)
{
    $name = sanitizeName($name);

    $key_plain = dechex($expires) . $name;

    // Get private key from a file
    $fp = fopen("private-key.pem", "r");
    $priv_key = fread($fp, 8192);
    fclose($fp);

    $res = openssl_get_privatekey($priv_key);
    openssl_private_encrypt($key_plain, $crypttext, $res);

    $base = base64_encode($crypttext);

    return $base;
}


/**
 * Veryfy if a license key is valid for the user
 */
function verify_license_key($key_base, $name)
{
    $name = sanitizeName($name);
    //echo "Verifying license key for $name: $key_base\n";

    $key_rsa = base64_decode(trim($key_base));

$pub_key = <<<RSA
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcExAHctl38IIUw+byiMt+X7Pe
jewWPzocUQNJN/apI55J3PV0Ri2UeZBJNT8AZy4FP31xpEpXfYNlK4N8gaZ3B6jO
4XgjaSwuFvSuXc9TUlelG4doAUbxD3lNF45oZ7tUvZ5+E0g1u0udMidkCq6cPsap
kgro0rKgHXNMQjxTeQIDAQAB
-----END PUBLIC KEY-----
RSA;

    $res = openssl_get_publickey($pub_key);

    openssl_public_decrypt($key_rsa, $key_plain, $res);

    $date = dechex(time());
    $datelen = strlen($date);

    if(substr($key_plain, 0, $datelen) < $date)
        throw new LicenseException('Expired license key!');

    $name_from_key = substr($key_plain, $datelen);

    if($name_from_key != $name)
        throw new LicenseException("Authorised name mismatch!");

    return true;
}

Here I'm testing it:

$key = generate_license_key('Pepíček Novák', time() + 3);

echo "LICENSE KEY: $key\n";

// OK
verify_license_key($key, 'Pepíček Novák');

sleep(5);

// FAIL - EXPIRED
verify_license_key($key, 'Pepíček Novák');
\$\endgroup\$
  • 1
    \$\begingroup\$ For the name sanitizing, take a look at stackoverflow.com/questions/1284535/php-transliteration. \$\endgroup\$ – ferada Oct 21 '14 at 20:53
  • 2
    \$\begingroup\$ for sanitizing, see also here. Your current function for example transforms ß to ß. Also, your code will break around the end of the year 2286, because unix times will be one character longer then. \$\endgroup\$ – tim Oct 21 '14 at 21:00
  • \$\begingroup\$ good points, I've implemented the changes. Now removing all non-ascii after replace, and separating date from name with a colon, instead of extracting it by length. \$\endgroup\$ – MightyPork Oct 23 '14 at 23:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.