Take the 2-minute tour ×
Information Security Stack Exchange is a question and answer site for Information security professionals. It's 100% free, no registration required.

So I'm trying to think of a good way to check if the license key a user supplies for a program is legit or not.

Let's suppose this is the way they get there license

  1. They make some type of purchase.
  2. A server generates a license based on their username
  3. Server gives license to user
  4. When user tries to use program for the first time, they use said license key
  5. Server checks to see if the license is legit.

The parts I have a question of is what's a good way to generate a "secure" license with just a username.

Currently I have something similar to this on a Java server.

username += "a long random string literal here";
        username = username.toUpperCase();
        md = MessageDigest.getInstance("SHA-256");
           md.update(username.getBytes());
            byte[] mdbytes = md.digest();

So as you can see, I'm adding a long random string to the username, and then making it case insensitive by changing it to uppercase.

I'm using SHA on the edited username.

So what exactly are the weak points in this? Is their a better way to check licenses?

Obviously the client would never see the generation method, so I don't see how they could generate their own licenses unless they obtained the string I'm adding to the username.

share|improve this question
1  
I think you've just re-invented the salted hash. Is there a reason you're changing it to upper case (which reduces the entropy)? –  Mark C. Wallace Oct 24 '12 at 11:00
    
@MarkC.Wallace Well most systems don't have case-sensitive usernames, and that was the solution I used to prevent that. –  Austin Oct 24 '12 at 11:02
    
The username is one of the inputs to the hash function; the output is where you want to preserve the entropy. Unless you've got a compelling reason to destroy the entropy, I'd keep the output of the hash function as a case-sensitive string. –  Mark C. Wallace Oct 24 '12 at 11:24

3 Answers 3

up vote 18 down vote accepted

The license generation method isn't really that important, as long as it's non-trivial. The trick is how your client verifies that the license is correct.

Let's say you do something like this:

BOOL verifyLicense (char* licenseKey)
{
    BOOL result = false;

    if(strlen(licenseKey) > 128)
        return false;

    char* url = (char*)malloc(1024);
    sprintf(url, "%s%s", LicenseServerBaseURL, licenseKey);

    char* response = http_get(url);

    if (strcmp(response, "OK") == 0)
        result = true;

    free(result);
    free(url);
}

This is pretty simple - we do some basic checks on the license key, create a URL that contains the license key, then do a HTTP GET to the server to check the license. If it returns "OK" we accept it.

The problem with this is that anyone can disassemble the client:

 ...
 push 06f2011c        ; address of url string
 call http_get        ; http_get(url)
 mov ebx, eax         ; store a copy of the result address
 push 04830040        ; ASCII "OK"
 push eax             ; address of response string
 call msvcrt.strcmp   ; strcmp(response, "OK")
 test eax, eax        ; if( ^ == 0 )
 jnz exit             ; skip if branch if non-zero
 mov eax, 1           ; result = true
exit:
 push ebx
 call msvcrt.free     ; free(result)
 push 06f2011c
 call msvcrt.free     ; free(url)
 ret

All I have to do is alter the jnz exit to a series of nop instructions, so the jump is never taken and the result is always true. That way, any license key is accepted. Even better, I could simply modify the start of the method to immediately set eax to 1 and return, so it never even bothers to ask your server for license validation.

So, how do we solve this problem? Unfortunately for you, we can't. You're running into the DRM problem, which essentially states that if you hand someone some data, they can always change it. No matter how much you obfuscate your code, it's possible for the user to eventually reverse engineer it and work out how to make changes, or extract data. If you encrypt content, you eventually have to have the key on the system in order to decrypt it, so there's always a way for the user to extract that key and decrypt the content permanently.

The best you can do is make it difficult, which involves a lot of time and effort. Eventually your software will be cracked and end up on a torrent site somewhere. It sucks for developers using this kind of business model, but it's the world we live in. My suggestion is that you go down one of two routes:

  1. Create a basic licensing system that works, and accept the fact that some people will pirate it. Serve your real customers by spending your development time actually making improvements to the software, rather than implementing and maintaining draconian DRM systems.

  2. Alter your entire development and business paradigm, so that your product is a service rather than a software application. SaaS has been very successful, since it's (practically) impossible to pirate services, and the model allows you to make real-time changes to your codebase, and perform analytics on usage.

If you go for option 1, don't involve a server for validation. It adds complexity, provides no added security, brings some privacy concerns, and makes it impossible for your users to use your software if your licensing server is down. Just stick with something simple, e.g. a hash of the username and a secret value on the client side. It's trivial to break, but it gives a minimum barrier to piracy.

share|improve this answer
    
Some programs "phone home" for license verification, but if they can't reach their home server, they assume that everything's working. So they'll be fine as long as you're offline (or, being nastier, you can edit your hosts file and redirect their home servers to the loopback address). –  TRiG Oct 24 '12 at 12:23
2  
@TRiG When all it takes is a mod to the hosts file to skip license verification, your anticopy is broken :P –  Polynomial Oct 24 '12 at 12:25
1  
My personal solution within .NET apps is to use an RSA keypair to authenticate keys, with the public key embedded in the application. I provide the user with a license key (randomly generated, but fits a pattern) and an authentication key (signature of the license key). Then I enforce integrity of the executable via code signing and perform in-code checks to make sure that the signature hasn't been stripped. It's still pretty simple to break, but it's hard enough that it prevents unskilled pirates and it's really reliable and easy to maintain. –  Polynomial Oct 24 '12 at 12:33
    
1+ Simple but great answer! Very comprehensible, thank you. –  cauon Oct 24 '12 at 15:41

If all you are trying to do is generate a license key that is hard to replicate then by using the username as part of the input to the hash you are actually going to make the license key weaker, and therefore easier to guess. The username is non-random and therefore decreases the entropy of the resulting license key, and the username can be guessed by the attacker who could then, making some assumptions about your method, brute-force your key much easier.

If you are adding a random string to the username then there's no good reason to use the username at all, just use all random data. The only reason you'd use the username to generate the license key would be to make it reversible.

share|improve this answer
    
Although mathematically you are correct, if the hashing algorithm is so weak that the username portion of a salted hash has a significant effect on the output, then the problem is the hash. Attackers should not be able to force a hash collision with a good hashing algorithm. If you're using random input to the hash generator, then you're just using a random number generator. There may be good business reasons why I'd want to be able to reproduce that this hash comes from this customer. –  Mark C. Wallace Oct 24 '12 at 10:58
    
I don't disagree with you @MarkC.Wallace, my point is that if it is not meant to be reproduced there's no point in weakening it in any way. –  GdD Oct 24 '12 at 12:55
1  
There is actually a social advantage of making the user's name part of the license key: If you hand a "bob-smith_09293..." license, Bob Smith is less likely to give his license to someone else because it's got his name in it. –  Sylverdrag Oct 24 '12 at 12:56
    
@Sylverdrag, apart from the shudderingly horrible privacy concerns with that, the question is regarding using usernames in hashed output, so they wouldn't be visible anyway –  GdD Oct 24 '12 at 13:00
    
@GdD The "shudderingly horrible privacy concerns"??? The customer receives a license key with his name on it, and it is stored on his computer and nowhere else. Oh, the horror! What a tragedy! –  Sylverdrag Oct 24 '12 at 13:07

Or you could send the user an email with a verification link. once they click the link your server knows that the software is legit. the app periodically 'phones home' to see if its been verified, and becomes feature-limited if it receives a negative response.

This also may protect against excessive multiple activations, as the server would know how many licenses a user has, and how many have been used.. Obviously all communication between the app and server is via ssl.

share|improve this answer
    
How does the app verify this in a way that cannot be tampered with? Your solution just obfuscates the licensing solution by sending a one-time link to the key over email. That's not the part that's hard to protect - it's the license verification that's difficult. –  Polynomial Oct 24 '12 at 10:29
    
For consumer software, phone-home DRM is absolutely the wrong approach -- it hinders legitimate customers moreso than pirates. The only place I've seen where phone-home is a boon is in business software where the application receives updates and patches automatically. –  Justin ᚅᚔᚈᚄᚒᚔ Oct 24 '12 at 14:27

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.