Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generating secure product keys: A comprehensive product key management library

0.00/5 (No votes)
9 Oct 2011 1  
This article provides an insight on how to generate uncrackable and flexible product keys. Strong product keys means less piracy and less lost revenue for a software publisher.

Introduction

This article provides an insight on how to generate un-crackable and flexible product keys. Strong product keys means less piracy and less lost revenue for a software publisher.

Background

License management is part of almost every commercially available product. The financial success of a commercial software solution depends heavily on correct licensing management. Finding ways to enforce licenses on software has never been easy. Software pirates always find creative ways to bypass licensing mechanisms, partly because the current hardware was not designed from the ground up with rights management in mind, partly because developers do not invest much time and resources into license management, some resulting solutions ending up easy to crack and bypass, thus causing loss of money. Part of license management consists of managing license keys. License keys are pieces of data used by software products to verify the fact that a particular customer has been granted a license to use a particular software product (by paying to use the product, for example).

Securing product keys

When generating a product key, one of the biggest challenges is to ensure that the key was generated by the product publisher and not by an unwanted party like a pirated key generator. In offline scenarios (when the key cannot be validated over the Internet against a database of generated keys), the key must somehow carry within itself the proof that it is "authentic". An approach is to layout the key binary data like this:

KEY = KEY_DATA | ENCRYPTED(HASH(KEY_DATA))

In order to validate the key, the key data is hashed, and the encrypted hash is decrypted. If the computed data hash is identical to the decrypted data hash, it means that the key used to decrypt the data is the correct decryption key. Now this is where it starts to get interesting.

Using symmetrical encryption to secure product keys

Many software authors resort to using a symmetrical encryption algorithm for generating product keys, and validating the product keys using an encryption/decryption key that is "hidden" within their product. This means that once a malicious party obtains the symmetric encryption key by reverse engineering the software, they are able to create key generators. Aside from the financial losses due to piracy, this also leads to big logistical problems for the software publisher. It usually means changing the symmetrical encryption key and releasing a new version of the product which only works with the newer generated keys. Which in turn means many support calls from customers trying to enter a newly purchased product key into an old version of the product, etc. It's worth mentioning that not all the symmetrical encryption algorithms are suitable to use in encrypting product keys, because some of them produce large encryption data sizes, which leads to unusable large product keys. AES, for example, is not a good approach because the block size has 128-256 bits so it's too large. Blowfish is a good algorithm for this because the block size is 64 bits.

Using public key cryptography to secure product keys

A much better approach for ensuring the authenticity of the product keys is to use public key cryptography, like the well-known RSA. This uses different keys for encryption and decryption. In this approach, HASH(KEY_DATA) is encrypted using a secret key and concatenated with the key data. To validate the key, the ENRYPTED(HASH(KEY_DATA)) is decrypted using a public key embedded with the product. If the decrypted data matches the key's data, it means that the key was encrypted ("signed") with the private key which is only held by the product publisher and not hidden in the product. The public key can only be used for decryption (signature verification), so this means that even if a malicious party knows this key, it cannot generate product keys.

So why doesn't everyone use public key cryptography to secure their license keys? The answer is that in public key cryptography, the digital signatures are very large. This means that the resulting product keys are very large, and no one would want a license key to be 200+ characters long. However, some developers use license files, instead of product keys. A license file contains a very large product key, sometimes in binary form, sometimes encoded using Base64 or other encodings. WinRAR is such an example. When you buy the product, you receive a license file instead of a product key. This approach has severe usability limitations though. You can't spell the product key over the phone, you can't print it on a sticker or on a DVD, you can't easily give license keys to your resellers, etc.

Introducing Elliptic Curve Cryptography

Elliptic Curve Cryptography (ECC) is an approach to public key cryptography based on the algebraic structure of elliptic curves over finite fields. It was invented in 1985 by Neal Koblitz and Victor S. Miller. One of the key advantages of ECC over RSA is that the keys are very small and just as cryptographically strong as the larger RSA keys. When using digital signature algorithms using ECC, the signatures are much smaller compared to the RSA signatures, making this approach suitable for generating product keys. One other important advantage of ECC is that there are many different valid signatures for the same data and signed with the same private key. This is of outmost importance, allowing to create many different keys without varying the input data via random values added to the data. This keeps the license keys small. The disadvantages of this approach are the difficulty of implementation and some patents held by various companies (notably Certicom) regarding various ECC techniques. However, there are some ECC implementations unencumbered by patents, like ECC over GF(2^n) with a polynomial base representation. This approach, combined with the Schnorr digital signature algorithm (patent expired in 2008), is used in our library to generate small, secure product keys.

A license key management library

I will present a software development kit which can be used to both generate and validate product keys of various sizes and strengths. The SDK is written in both C++ and C#, such that it is available in both native and managed form. It has interfaces in C++, C, and managed code.

The concept of "License Key Template"

A license key generally has the form XXXXX-XXXXX-XXXXX-XXXXX-XXXXX. It is comprised of a number of character groups, each group having a certain number of characters. Internally, the license key contains binary data and a digital signature of that data. A license key template is a collection of parameters specifying how the license key is encoded, how many groups of characters it has, how many characters per group, the size of the data we want to embed, and the size of the signature. The sample code below uses the C++ API of the library.

// declare a license key template object
LicenseKeyTemplate  tmpl;
// the key has 5 groups of characters
tmpl.SetNumberOfGroups(5);
tmpl.SetCharactersPerGroup(5); // there are 5 characters in each group
tmpl.SetGroupSeparator("-"); // you can specify any character as group separator
tmpl.SetSignatureSize(109); // the signature part of the key has 109 bits
tmpl.SetDataSize(16); // there are 16 bits of data
// add a ProductId integer field, 16 bits in size
// and starting at offset 0 in the data bits
tmpl.AddDataField("ProductId", FIELD_TYPE_INTEGER, 16, 0);
// so the license key has 5 groups x 5 characters
// per group x 5 bits per character = 125 bits. From these,
// 16 bits are data (we can store a product id
// for example) and 109 bits are the signature

The template parameters can also be exported to XML and imported from XML.

tmpl.LoadXml(strXml); // load the template from xml
strXml = tmpl.SaveXml(bSavePrivateKey);
// save the template in xml. If the template is used only
// for key validation purposes (for example into
// a software product) you can omit saving the private key.

Recommended signature sizes

When you set a certain product key signature size via tmpl.SetSignatureSize(), the library automatically chooses a set of encryption parameters such that the resulting signature is of the specified size. The following is a list of recommended signature sizes for use in generating license keys:

  • 86 bits - This signature size uses a 54-bit ECC key and a 32-bit hash function. It allows for very small license keys (5 groups of 4 characters, or 4 groups of 5 characters, or a single group of 20 characters, etc.). It can be secure enough for small projects, and it's certainly not so vulnerable to reverse engineering like a hidden symmetrical key. If you choose your keys to have 5 groups of 4 characters (100 bits in total), you can use the remaining 14 bits to store various data.
  • 109 bits - This size uses a 73-bit ECC key and a 36-bit hash function. It has similar strength to the Microsoft Windows keys. If you choose your keys to have 5 groups of 5 characters (125 bits), you will also have 16 bits for various data you may want to embed in the keys (like a product ID, bits for various features, etc.). Of course, you can choose your license keys to be larger (like 5 groups of 6 characters or whatever) if you need to store more data in the generated keys.
  • 161 bits - This size uses a 97-bit ECC key and a 64-bit hash function. If you choose your keys to have 6 groups of 6 characters (180 bits in total), you also have 19 bits remaining to store various data.
  • 322 bits - This is the maximum possible size. It uses an 161-bit ECC key and the resulting keys are very secure but also a bit large.

Generating license keys

Once we have set up a template for the license keys we want to generate, we are ready to generate keys. But first, we must set the private key in the license key template:

tmpl.SetPrivateKey(strBase64Key); // set the private key. 

If we are generating keys for the first time, we can generate a private/public key pair and store them securely for future use:

// generates a private/public key pair,
// according to the signature size set above.
tmpl.GenerateSigningKeyPair();
// store the private key in the supplied buffer
tmpl.GetPrivateKey(privateKeyBuf, &privateBufLen);
// store the public key in the supplied buffer
tmpl.GetPublicKey(publicKeyBuf, &publicBufLen);
// encode the 2 buffer in Base64 and store
// them securely into a text file, etc.
// ...

Now we are ready to generate the license keys:

LicenseKeyGenerator keyGen; // create a license key generator object
// specify how the generated keys would look, what is the
//signature and data size, etc.
keyGen.SetLicenseKeyTemplate(tmpl);
const char * key = keyGen.GenerateLicenseKey();  // generate a license key
printf("%s", key); // print the generated license key

Generating license keys locked to a specific customer name or computer

Sometimes we need to generate license keys locked to a specific customer name. For example, we want the customer to enter both his name and his license key in the registration dialog, and we want the license key to be valid only for that customer name. In this way, we can prevent sharing a product key among many users, because a customer may not be willing to publicly share his full name along with the product key.

Introducing the concept of validation data fields: this data does is not embedded in the license key like the key data, but it must be supplied at validation time. At license key generation time, the license key signature is computed from both the key data and validation data, concatenated together. At validation time, the validation data used at generation time must be supplied in order to successfully match the license key signature. Programmatically, we just need to add a validation field in the license key template:

tmpl.SetValidationDataSize(1024);
// we assume that the UTF-8 representation
// of the customer's name does not exceed 1024 bits. 
//If it's smaller, the remaining bits will be padded to zero.

tmpl.AddValidationField("CustomerName", FIELD_TYPE_STRING, 1024, 0);

When generating keys, the validation field must be set for each generated key. For example:

for (int i = 0; i < numberOfKeysToGenerate;  i++)
{
    keyGen.SetValidationData("CustomerName", customerNamesStringArray[i]);
    keyGen.GenerateLicenseKey();
    // the generated license key can be validated only
    // if the same name is supplied at validation time
}

Another scenario is when we want a license key to be locked to a specific computer. If we have the means to obtain a computer hardware ID from our software (for example, the first hard drive's serial number), we can use this as a validation field. Of course, the hardware ID must be supplied both at generation time and at validation time.

tmpl.AddValidationField("HardwareId", FIELD_TYPE_STRING, 1024, 0);

// key generation sample
keyGen.SetValidationData("HardwareId", strHardwareId);
keyGen.GenerateLicenseKey();
// key validation sample
keyValidator.SetValidationData("HardwareId", GetComputerHardwareId());
keyValidator.SetLicenseKey(strLicenseKey);
if (keyValidator.IsLicenseKeyValid())
{
    // ...key is valid and was generated for this computer
} else
{
    // ...key is invalid
}

Validating license keys

For license key validation, we must first set the public key in the key template:

tmpl.SetPublicKey(strBase64PublicKey);

Now we are ready to validate keys:

LicenseKeyValidator keyVal;
keyVal.SetLicenseKeyTemplate(tmpl);
keyVal.SetLicenseKey(strLicenseKey);
if (keyVal.IsLicenseKeyValid())
// check if the digital signature is valid
{
    // license key is valid, now extract the data from the license key
    keyVal.QueryLicenseKeyData("ProductId", dataBuf, &dataBufLen);
    // check if the productid is the one we expect. Etc.
    //...
} else
{
    //invalid license key
}

Note: Although this it is beyond the scope of this article, the license key validation must, at a minimum, be done in multiple places within the source code and at multiple times during the program execution in order to harden the license enforcement process.

Using the code

The provided library (library source code, sample source code, and binaries) may be freely used, but please note that some key source code files containing the Elliptic Curve Cryptography algorithms are not included here. If you need full control over your licensing (a good idea), you can obtain the complete SDK with full source code from http://www.softactivate.com. Most of the time it's not a good idea to use a DLL containing your license key validation code because it can easily be hacked/replaced with a bogus DLL, it's better to embed the library code into your product directly.

History

  • Initial publication.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here