Using encryption to verify a license key
A long while ago I had this project, which allows you to define software licenses that you can distribute. The basic idea is pretty simple, we want to be able to define a key (needs to be short and copy/pastable) that we’ll be able to provide to our code that is running in a separate environment. In particular, we have to deal with the issue of not being connected to a central server.
That sounds like a really hard problem, but it turns out that it is a pretty simple solution, if we use public key cryptography. Typically you’ll utilize public key cryptography for encryption, but you can also use that for signing. The idea is that we can use the ability to sign the key using a private key, then validate it (potentially offline) using the public key.
Licensing can be complex, so we are going to punt all of that to someone else. In this post I’m going to just discuss how we can sign a piece of data and then verify it. We’ll start by generating the keys, this is an action that you should only do once:
Here is the output of this code:
You can now embed the public key in your software, while keeping the private key hidden.
With that in place, we can now sign a license. But what is a license? At its most basic form, it is a set of attributes that describe what your license allows. As such, we can use a Dictionary<string,string> to define the capabilities of the license.
With that in place, we can write very simple code to generate the license text:
And here is the result:
The last part to deal with is verifying that the license is valid, we can do that using the following function:
If the license options are the same as the one we signed and the cryptographic signature is a match, we can safely return the license options. Otherwise, we’ll return null.
As I said, this is pretty simple code all around. And doesn’t do much, but it means that you can let the framework carry most of the load.
Features such as enabling specific capabilities for a license, expiration, etc are all possible. Define an ExpirationDate property and have your software react to that, for example.
A few words about this implementation, however. I’m relying heavily on the .NET framework, obviously. But beyond just using the cryptographic primitives, we also have to take into account a bunch of other aspects.
For example, I’m not bothering to normalize the license. Instead, I rely on the fact that the .NET Dictionary will iterate over keys in insertion order. Note that any change in the actual data will result in a verification fails.
This is a pretty simple design, but it ensures that you cannot “crack” the algorithm used to generate the license keys. Of course, users can still always just patch the isValidLicense function, instead .
Comments
How do you store and retrieve the public key PEM? How can you prevent the user from modifying the license to their liking and then generating a new signature with their own key pair?
How about using JwtTokens for this? Comes with added bonus of expirationTime. Let the framework work even harder for you :)
// Ryan
@ Chris B, the user cannot modify the license with only the public key. A license with just one bit modified would be invalid when validated again the private key.
// Ryan
@Chris B says "How do you store and retrieve the public key PEM? How can you prevent the user from modifying the license to their liking and then generating a new signature with their own key pair?"
The short answer is no matter where the public key PEM is stored the end user can still modify the license and generate a new signature with their own key pair. If something is digital it can be hacked.
The controller of the licensing has to decide how much time and effort to put into attempting to make cracking the licensing harder. At the end of the day if the end user is willing to put in an infinite amount of time, money, or effort, they will be able to defeat the licensing eventually.
Some examples If the public key PEM is in the application directory, the user can just replace it. If the public key PEM is embedded in the application, the user can patch the licensing function or change the public key bytes in the application and overwrite them with their own key bytes. If the public key PEM is provided via some online endpoint, the user can make a proxy and direct the application to their proxy.
There can be solutions to some of the above, but it takes more effort to stop the end user from doing those things.
Chris,
You can add the PEM as an embedded resource. And the same thing that is preventing them from modifying the code that does the licensing check.
I think this approach might have a disadvantage that ie. JWS wouldn't have. The order of properties in JSON objects, according to the standard, shouldn't matter. Using this approach, you heavily depend on
SerializeObject
on the license signer and licensed application to always produce the same output, which might be influenced by something out of your scope. (For a 3rd party library, it could mean you could never update that library, if it were to modify the serialization in any way?) JWS avoids that by signing the Base64 version of the JSON text, and storing the signature separately from the signed content. That way, it only has to verify the signature of the Base64 text, can just decode and parse it as JSON, and trust the object to be valid.Hangy,
You are correct, in the sense that this is a limitation. Note that JWT has to protect itself from many more attacks. For example, what if I provide a JSON bomb of some kind? There isn't something like Billions Laughs for JSON, but using this approach, you'll need to first parse the JSON before you can auth. In general crypto terms, you're better off (by far) in first validating, then acting on the data.
In this scenario, however, the situation is a bit different. Because we would like the properties of the JSON to be readable.
Note that I'm not worried about the details of SerializeObject, since I can just say that you format the JSON data without the sig, and can do that in any language / format using streaming API.
Is the reason why you want the data to be readable for support purposes? What do you think about using a comment block in the license file instead? (Yes, could be modified by the end-user.)
My point about the serialization is that the order of the dictionary enumeration is not guaranteed in any way. It could be changed in .NET, or JSON.NET could change the way it serializes the dictionary. Not only does .NET not guarantee that the enumerating over a dictionary is in the insertion order (ie. it can be different if a value was removed, see this answer on SO), but also RFC 4627 also defines a JSON object as
Which kind of makes the approach of embedding the signature in the signed object (not quite, but you know what I mean? 😅) look somewhat unsafe, because it's subject to the specific implementation detail?
Hangy, Making it readable is a lot nicer in terms of UX. Users can see the details there, making it simpler all around.
Please note that I'm relying here on the fact that the iteration order on a dictionary that didn't have deletes is fixed to insertion order. That is not something that can be changed (since that would break backward compact).
Note that JSON.Net creates the dictionary in the order it sees the items.
You can sort them upfront if you need to, but you get complex when you deal with the generic scenario (nested objects, items in arrays, etc). Not generally worth it.
Comment preview