[xplorer²] — OpenSSL/RSA license key generator
home » blog » 27 June 2010 [programming]
 

If you try to make some money out of your programs, you'll need some sort of registration key to license the software. You have all seen keys like VP9VV-VJW7Q-MHY6W-JK47R-M2KGJ used to activate windows and other microsoft programs. There are many ways to generate license keys but the main principles are:

  • Each key should be unique (and may include customer details)
  • They shouldn't be easy to generate by pirates

Generating keys isn't easy for the average windows programmer. It is a separate skill especially when considered as part of the total protection against piracy, reverse engineering etc. There are products you can buy to add protection and licensing to your code (e.g. WinLicense, Armadillo etc), or you can roll out your own key generator like I did with xplorer².

Why reinvent the wheel? Believe it or not sometimes off the shelf protection solutions are easier to crack because so many programs use them, there are automated tools to strip their protection! They also use some obscure tricks to protect the software from debuggers and other tools used by the pirates, which may cause antivirus programs to mistake your software for a virus. That's not going to help your revenues. Finally from what I could tell, these licensing tools do not support secure public/private key RSA algorithms (or their customer support wasn't tech savvy), so you risk being keygenned.

The worst case scenario for your licensing is your key generator scheme being discovered by some mad dog pirate, who then goes on to distribute keygens. People who get hold of these keygens can offer themselves free license keys for your software and your business is losing money.

I used an amateur license key scheme in xplorer²; it took a couple of years before the first keygens appeared back in 2006(?). These first crack attempts were not very good so it was easy for me to detect legitimate from keygenned keys. Over the years the pirates became more sophisticated as they could read the code I used to fight against them so they improved their keygens. To this date this cat and mouse story continues, but I believe that soon a perfect keygen will emerge. (PS. none of the existing keygens work properly). So I'm looking for a more secure alternative.

RSA cryptography key generators

Using cryptographic techniques it is possible to come up with license keys that are 100% proof against illegal key generators. RSA is a heavy duty asymmetric encryption algorithm using some fancy mathematics and private/public keys. The idea is the same as digital signatures used in emails or authenticode:

  1. Create a pair of RSA public and private encryption keys
  2. Use the private key to 'sign' the registration code (it could be anything, e.g the customer name and email)
  3. Use the public key to verify the signature in the program

Although the RSA algorithm is well known, its strength lies in the separation of the encryption and decryption keys (unlike symmetric encryption algorithms like blowfish or AES). The pirate can only see the public key in your software. The private key is safely kept on your own webserver that sends out the legitimate registration codes. As the private encryption key is a huge prime number, it is computationally infeasible (or let's say very hard indeed) to derive the private key knowing just the public key. So your license key generator is uncrackable.

Having a bullet proof key generator doesn't mean you are saved from software piracy. You will be cracked in other ways. Hackers can decompile your executable, find where you do the signature check and disable it with a patch. But this is hard work for them, every time you release a software update, they will have to reapply the patch. An unauthorized key generator is much more convenient for the pirates.

Practical RSA software licensing

RSA encryption boils down to large number arithmetic (exponentiation and modulo division of numbers having in excess of 512 bits). There are many open source big-num libraries, usually as part of crypto libraries (crypto++, openssl, mozilla's NSS et al). For windows programming there is also MS CryptoAPI, present on almost all windows boxes (even in windows 95). There also is a selection of algorithms, RSA vs DSA or elliptic curves.

I did not make an exhaustive enumeration of pros and cons of each alternative. I didn't like CryptoAPI much because it is too complicated and the keys are too opaque for interoperability with other packages. I chose openssl RSA simply because it is as good as the next crypto library and there are a few samples floating around. More importantly openssl is also usable via PHP scripts; the scripts that will issue the license keys will be most probably running on your webserver and PHP is handy for this task.

My starting point for the RSA key generator code was this article. It is a very good introduction with sample code. But the devil is always in the details. Which padding scheme do you use? Do you go for MD5 or SHA-1 hashing? Are we big or little endian? And how do you make sure that the keys signed by your PHP scripts will be validated from your C/C++ code? All will be revealed below.

OpenSSL windows development platform

To develop and test all aspects of the key generator on windows you will need two downloads. For the C/C++ part you need the openssl windows headers and binaries that will allow you to compile and link against openssl. For PHP development you need a WAMP distribution that conveniently packages the Apache server, MySQL (not necessary for us) and PHP (including the scriptable version of openssl).

I downloaded the full version of the win32 binaries (Win32 OpenSSL v0.9.8o) and you may also need the Visual C++ 2008 Redistributables if you find that the openssl command line tools return funny errors (openssl.exe complained about missing configuration files). After installation make sure you add the openssl folder to the PATH environmental variable so that ssleay32.dll and libeay32.dll can be found. Also add the openssl INCLUDE and LIB folders to the build environment. I didn't manage to link statically to the openssl libraries with VS6 but dynamic linking works fine.

To run PHP first you need to run Apache. The WAMP distribution mentioned above is very convenient installing all the components, in a win32 friendly way: everything ran smoothly without messing about with configuration files that linux types are so fond of — we have better things to do in our lives with windows <g>. The only problem I had was that Apache service wouldn't start unless I was logged with administrator credentials on a virtual XP machine. The wampserver icon would be 1/3 red otherwise and localhost was inaccessible.

Considering that I never ran Apache/PHP in my life, the french guys that manage wampserver did an excellent job. The installer creates a C:\WAMP\WWW folder which is the dummy website. Save your PHP files in there and they can be accessed from your browser window as http://localhost/test.php. Mind to enable openssl PHP extension from the wamp taskbar context menu (see the pic to the right). The PHP version even has decent documentation. Last but not least check that this offline server matches the actual PHP and openssl version that your real website uses (check using phpinfo). enable openssl for PHP

The openssl C library on the other hand is poorly documented. There are few explanations, weird concepts like BIO buffers, and very few actual code samples mostly focusing on the secure sockets layer part, not the digital signatures. In the end I found an online book with a lot of information. But hopefully all will become clear in the code examples further down.

Step 1. Generate your encryption keys

You will need a pair of private and public RSA keys. The easiest way is to open a console window, switch to the openssl installation folder and execute the following commands:
openssl genrsa -out private.pem 512
openssl rsa -in private.pem -out public.pem -outform PEM -pubout

This will create 2 plain text files with the private and public keys in PEM format. I don't know what this is either :) but it can be used straight in your code below.
generate encryption keys

The most important parameter is the bit length of the encryption. I have opted for 512 bits which means that the signature (with padding) will be approximately 512/8=64 bytes long. Longer bit lengths result in stronger encryption but remember that we are not building anything that will affect national security, and we don't want to end up with too long software codes (registration keys).

Step 2. Create and sign a registration key

When somebody buys your software, the ecommerce provider that handles the credit card payments (e.g. Plimus) will do a HTTP POST (or GET) request to your webserver with the customer details expecting a registration key in response. The idea is to bundle the order parameters (date, customer email, number of licenses and what have you) in a text string which will be the 'message' to be signed, e.g:

1.001|KT.1|1|26.06.2010|0|Joe Bloggs|some@email.com|

We calculate the message digest (20 bytes for SHA-1) and RSA sign it using the default PKCS1 padding scheme. The details are explained in this tutorial. The resulting signature will be a very long number (~512 bits) which for convenience is base64 encoded so that it can be included in an email as text. Here's the PHP code that does the signing:


<?php
// somehow bundle the order details in a string...
$data = "1.001|KT.1|1|26.06.2010|0|Joe Bloggs|some@email.com|";

// the contents of PRIVATE.PEM file we generated earlier
$private_key = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAJ9mjRfj3xkXNAIdvk/QVi+xJruhn74uXH2fPz8tOK1sJx+zI64b
wchdFThqlf368Yid9CRjhni/VpYUeH5OpJkCAwEAAQJBAJZ2UNyqJh8i/d65gLkK
KTCfgbY/G/CDBp81wJe78fIP1TxjO41E/BeFmP5YxLdhLwt9N0dR0IDgzRrpLvaS
6rkCIQDSMEhl43MuZE3qp1o8Sg/oK35l/lQHLkvzC2JzEBic1wIhAMIkffwFPLxb
fHBHnGzvzqNgzAR70SSWAFqhEuXjy6wPAiB2o8o5uKjLBtL0IIJOhX91DRfLekLz
yq4UAdkQGYXLAQIgd8UpkAPvH0jVcTNMlowvD+3Uj2OPeCGuIDtEvFyXNxMCIBvy
9gDnf7ZNXwI+3MRVVFgsytQgqIhv9aV5LRr9WvdI
-----END RSA PRIVATE KEY-----
EOD;

$binary_signature = "";
$ok = openssl_sign($data, $binary_signature, $private_key, OPENSSL_ALGO_SHA1);
echo $ok . ' signature: ' . base64_encode($binary_signature) . '\n';

?>

All pretty standard PHP stuff using some openssl functions to sign the text with our private key. We can use the PEM text file verbatim to initialize the RSA key variable. It goes without saying that the private key should be kept for your eyes only — don't publicise it in your blog <g>. This PHP script resides in the webserver and (I hope) cannot be seen directly by cheeky pirates. For extra safety only allow this script to run from known IP addresses (eg your ecommerce provider only).

The registration key you email to the customer is 2 text lines, the original order details and the base64 encoded signature thereof as such:

1.001|KT.1|1|26.06.2010|0|Joe Bloggs|some@email.com|
OW5wH10Jgz6F+f+cCHE5gzzSCtWPw5RTlhXrfurtus+uk6z5UjrIaM+W+4AZYObI8o5oC8xdDCLafNNmstsOww==

It looks a bit funny and it is quite long but it is bullet proof! If the key information is tampered the signature will no longer match. As only you know the private encryption key, nobody can patch the registration code. You are in keygen heaven!

If you are concerned with the size of the key, you can use a weaker encryption key pair (eg 256 bits) but as CPU power increases chances are that pirates may brute-force their way to find your private key and the game is lost. I don't think people will need to type this key anyway, they can just copy/paste the text from the registration email. For extra safety you can calculate a simple checksum and tack it on at the end of the key (unlock code).

Step 3. Validate a registration key

The customer has received the unlock code and somehow pasted it in the program. But is the key any good? Here is the validation procedure:

  • Split the code in two parts, the order details and the base 64 signature (both text strings)
  • Calculate the SHA-1 hash of the order details part (unencrypted test signature)
  • Reverse the encryption of the base64 signature using the public RSA key; if it matches the test signature the code is valid, otherwise reject it

Here is a sample C++ program that links to openssl to perform these checks:

#include <openssl/sha.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <string.h>
#include <assert.h>

// get 64-byte signature from its base64 encoded version
int unbase64(char *input, unsigned char **out)
{
   BIO *b64, *bmem;

   // base64 encoding is longer than the original 'string'
   int length = strlen(input);
   unsigned char *buffer = (unsigned char *)malloc(length);

   b64 = BIO_new(BIO_f_base64());
   BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

   bmem = BIO_new_mem_buf(input, length);
   bmem = BIO_push(b64, bmem);

   length = BIO_read(bmem, buffer, length);
   buffer[length] = 0;

   BIO_free_all(bmem);

   *out = buffer; // caller frees
   return length;
}

// verify plaintext key is matching signature (base64 encoded)
int VerifyKey(char* key, char* sig_b64)
{
   unsigned char hash[SHA_DIGEST_LENGTH/*20*/];

   char pub_key[] = {
// PEM format just like the public key file saved by openssl
"-----BEGIN PUBLIC KEY-----\n"
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ9mjRfj3xkXNAIdvk/QVi+xJruhn74u\n"
"XH2fPz8tOK1sJx+zI64bwchdFThqlf368Yid9CRjhni/VpYUeH5OpJkCAwEAAQ==\n"
"-----END PUBLIC KEY-----\n"
   };

   unsigned char* signature;
   int length = unbase64(sig_b64, &signature);

   // WARNING: no error checking for brevity
   BIO* bio = BIO_new_mem_buf(pub_key, sizeof(pub_key));

   RSA* rsa_key = 0;
   PEM_read_bio_RSA_PUBKEY(bio, &rsa_key, 0,0); 
   assert(64 == RSA_size(rsa_key)); // 512 bits (/8=64)

   SHA1((unsigned char*)key, strlen(key), hash);
   // padding fixed to PKCS1 v1.5 (shortcut to RSA_public_decrypt)
   int ret = RSA_verify(NID_sha1, hash, 20, signature, length, rsa_key);

   RSA_free(rsa_key);
   BIO_free(bio);
   free(signature);

   return ret;
}

void main(void) 
{
   char *key = "1.001|KT.1|1|26.06.2010|0|Joe Bloggs|some@email.com|";
   char* sig64 = 
   "OW5wH10Jgz6F+f+cCHE5gzzSCtWPw5RTlhXrfurtus+uk6z5UjrIaM+W+4AZYObI8o5oC8xdDCLafNNmstsOww==";

   if(VerifyKey(key, sig64)) 
      printf("key OK!\n");

   // if the key is tampered...
   char *ke2 = "1.001|KT.1|1|26.06.2010|0|JIM Bloggs|some@email.com|";
   if(!VerifyKey(ke2, sig64)) // verification fails!
      printf("dodgy key!\n");
}

This C++ demonstrates how awkward and unintuitive the low-level openssl can get. I haven't got a clue what these BIO containers in unbase64() function do or if handles and memory is leaked. It works validating RSA signatures, that's for sure. Note that the hash (digest) and padding of the verification stage match exactly that of signing earlier.

You must be careful not to reject valid registration codes because of formatting errors. You must remove extra whitespace and newlines otherwise the hash won't match. Encryption purists may also get upset from the apparent lack of security, but see this link for adding blinding, whatever this may be. Again remember that you are not dealing with credit cards or national security here, so you can relax your high standards <g>

Isn't this magic? With a mere 100 lines of code you have yourself the bare essentials of a potentially uncrackable software key scheme! We don't care that anybody can potentially read our public key as they cannot infer the secret private key from it — not without a cluster of cray supercomputers anyway.

The only improvement I am considering is to ditch the C part of openssl verification for something more reasonable and less mysterious like this bigdigits library. It is going to be fun extracting the prime numbers and exponents from the PEM private key files, and do the RSA modulo arithmetic manually, but more on that in a future blog post!

Post a comment on this topic

 

 

What would you like to do next?

Reclaim control of your files!
  • browse
  • preview
  • manage
  • locate
  • organize
Download xplorer2 free trial
"This powerhouse file manager beats the pants off Microsoft's built-in utility..."

download.com
© 2002—2010 Nikos Bozinis, all rights reserved