Robust serial keys validation: an idea.

I think I had a good idea (maybe it's obvious and/or well known, still to me it wasn't)

The objective is to create a hacker proof function that validates keys (or rejects them) such that, even if the hacker has full access to code, he cannot make a keygen-find the keys. (of course he can always modify the binary to avoid the validation entirely, but that's still much better than giving him full access to the keys).

if you use code like

Code:

if (key="fgdjnlkr" or key="oipp321" or key=...) then validated=1;

the hacker, reading the code (or decompiling the binary) can obviously find the keys.

For my older games I personally used numbers for the keys, so performing some math operations usually involving mod, checked if the number was valid or not. Naive example:

Code:

if ((key mod 49879)==0) then validated=1;

of course you can complicate it but most likely it will be easily invertible and it will be possible to make a keygen (i.e. list all valid serials).

Many many mac games use to tie a valid serial to the name or email address of the customer with some hash (say md5). Something like:

Code:

if (key==md5( NameString+"salt")) validated=1

(Where salt is a string you chose) The problem with this is that, even though md5 is virtually impossible to invert, the hacker has access to your code! So he can simply read the "salt" you're using from the binary, and create a keygen easily (key=md5( NameString+"salt"))

So what's the new idea?

You start with a list of randomly generated strings of a length of your choice. These will be your valid serials keys, which you will keep for yourself. You need one string for each license you are going to sell.

say "string1", "string2" etc

Then you hash them, md5 will be perfectly fine, you don't even need a salt.
[edit: to avoid the use of pre-created tables, you still better use a salt]

So you get "String1md5", "String2md5"...

Now you checking function will be:

Code:

if (md5(key)==String1md5 or md5(key)==String2md5 or...) then validated=1

Since md5 is virtually uninvertible, there's no way a hacker can find valid key strings, even if he has full access to the code!

Possible drawbacks are that if you have many valid keys the function might be slow (no problem for a local app, more of a problem for a server).

AquaticPrime's keys are pretty dang unbreakable*, but the weak link is the actual application. Clever kids will walk through it in the debugger and change your validation method to always return true. Then it doesn't matter how unbreakable your key is.

Copy protection is an interesting intellectual challenge, but making something "unbreakable" is hardly worth the time.

* provided you don't do anything stupid like publish your private key, and provided there are no great breakthroughs in efficiently cracking RSA

Quote:I don't completely understand RSA, I thought that using the public key you could still get a valid name-serial couple (starting from the serial), but probably I'm wrong.

If you encrypt data with the private key, anyone with the public key can decrypt the data (this verifies the sender). If you encrypt data with someone else's public key, it can only be decrypted by that person's matching private key (this verifies the recipient).

With license keys you encrypt [an SHA-1 has of] the user name with the application's private key (which is only on [i]your[i/] hard drive) and the application decrypts the data using the public key (embedded into the application).

If someone has the license key and the public key, then they can decrypt the license key, and get whatever was encrypted, yes. If you simply encrypt the user's name, then that's what you'd get back.

But that's why you create a SHA-1 of the user's name (and any other information). The result is a fixed-length digest from a one-way hash (which means you can't figure out the source data from the hash).

If you encrypt the hash digest of the user's name, then decrypting the license key would simply result in the hash itself, which is useless because they can't just type the hash into your app to validate with the key. Your application will take the user name, create a hash of it, decrypt the license key to get that hash, and compare the two. If they're the same then the license key/name pair is valid.

(An attacker could, however, change your validation code so that you can simply input the hash itself to match the decrypted has from the key, but that'd be pointless because it'd certainly be a whole lot easier to just bypass the validation check altogether.)

Quote:AquaticPrime's keys are pretty dang unbreakable*, but the weak link is the actual application. Clever kids will walk through it in the debugger and change your validation method to always return true.

Don't have a validation method. Use an inline function and check in a bunch of places.

Though I guess binary search and replace would do the trick there, so I supposed you'd have to change it up somehow by seeding the inline function with some kind of random string to do something with to change the code a bit.

FreakSoftware Wrote:Though I guess binary search and replace would do the trick there, so I supposed you'd have to change it up somehow by seeding the inline function with some kind of random string to do something with to change the code a bit.

That's why it's "impossible" to crack-proof. No matter what, the check can be traced down -- even if it's tricky to trace. I read an article a while back about a game company which did tricky macros (to inline checks randomly) to trip up the crackers and it worked for a while, but eventually succumbed. But your angle on it is the same as mine: if you really want to play cat and mouse, then don't be so obvious as to how you're doing your check, and instead, inline it and switch things up a bit. At least that way they'll have to *look*. Heck, they might even have a little fun!

This is why I like the idea of drawing the line at AquaticPrime (plus maybe a little tricky stuff to make it fun). It's virtually "hack-proof" (if you can call it that) from the standpoint of the customer who is willing to pay. The app actually has to be cracked (either run-time injection or binary mod); they can't just casually pass along an anonymous serial number.

So with AQ you eliminate one part of the population from stealing your stuff. The other part can't be stopped anyway; merely slowed down. At least, that's the way I see it.

AnotherJake Wrote:I read an article a while back about a game company which did tricky macros (to inline checks randomly) to trip up the crackers and it worked for a while, but eventually succumbed.

Heh. I guess it's a balancing act between how easy it is to develop a method to make it difficult to crack and the benefit to the cracker for cracking it. What I'm think here is that, if it's a AAA game, you're going to get cracked regardless. If someone wants to play that game, they have to either pay for it or crack it. Whereas if it's an application, then there are probably two or more others just like it, and as a cracker you might as well spend the 30 minutes to crack that other one instead of this one which will take 5 hours to track down the pattern.

-

Currently I do one simple inline in a few places. I'd like to think of something I can do in a few minutes to change that up. Every couple of months I'd like to do something that makes it a little different as long as it only takes a few minutes of my time. That way they at least have to crack every version. Who knows, maybe it'll only make 1 sale difference because one guys needs to use one feature that's only in the latest version that hasn't been cracked yet and it's unique. 10 minutes for a single $30 sale.... so it can conceivably pay off even if it's a few grains of sand on the beach.

Najdorf Wrote:Still I'm pretty satisfied with my method, it's very simple and I think it's pretty robust if you put a salt in your md5.

Naj, if you sell 5,000 copies of your game, then that means you have to have 5,000 MD5'd keys in your game. Are you seriously considering that?

Not to mention MD5 is dead cryptographically. It can "easily" be attacked with brute force these days. There's an online database of over a billion entries for reverse lookups as well. More applicable to passwords than license keys but if your license keys are short enough, it's still not that much work. As soon as one is reversed, it'll be everywhere in serial databases.

With RSA and SHA-1, there's no generating a fake key, or reverse engineering one. And no 5,000 embedded keys required to boot.

The only "downside" is that the generated keys are long, but that's what copy-paste is for.

FreakSoftware Wrote:Naj, if you sell 5,000 copies of your game, then that means you have to have 5,000 MD5'd keys in your game. Are you seriously considering that?

Haha, yeah
I agree it's not ubercool.

Quote:Not to mention MD5 is dead cryptographically. It can "easily" be attacked with brute force these days. There's an online database of over a billion entries for reverse lookups as well. More applicable to passwords than license keys but if your license keys are short enough, it's still not that much work. As soon as one is reversed, it'll be everywhere in serial databases.

Tables are no longer valid if you add a "salt": (i.e. I md5 (key+"mystring"), then again check if md5 (key+"mystring") belongs to my phenomenal 5000 item array. [edit: wait, they're actually still valid... still the salt makes the keys longer, impossible to find in tables]

I don't know, you have your points of course, mostly the avoidance of the mega array, but then this way is simpler (for instance you can put valid keys in a file for your reseller to deliver, not having to integrate with php calls that pass the data in, call your RSA script etc... [what if the customer has a strange name with accents...])

RSA/SHA-1 code is freely available. Just look at AquaticPrime's code and there ya go. Integrating with to get the serial number by a call from payment handler was only as difficult as typing a URL into a field in their admin page, for me. AquaticPrime even has php code to generate licenses. (I just use a command line app written in C. A whole 50 lines of code that's pretty much already written for you.)

Quote:what if the customer has a strange name with accents...

It's not an issue at all.

--

It's up to you of course, but it's hardly any simpler to do it your way.

As far as code security goes, I have a number of ideas. You could put an inline function in many important places in you code (particularly, functions that must be called to obtain functionality, but are not called often) that expands to some validity check so that the hacker at least has a hard time changing all of those functions (finding a way to vary them automatically so they compile differently would be a neat addition).

Furthermore, if you're really obsessed with security, you could implement any number of code obfuscation techniques. As an example, you could encrypt your code so that your validation code is dynamically constructed at run-time (and perhaps that code is encoded as well ). Of course, the hacker could use a debugger to step through your code and watch the code be unpacked, but there are techniques in which your code can detect if there is a debugger attached (or whether it is running in a virtual machine). All of these techniques, of course, just make the hacker's job harder, but not impossible. Maybe if you implement enough of these, you could just hope that the hacker will eventually give up and move on to an easier piece of software.