0

Like a lot of modern software companies, my company uses product keys to check what contract a user has for a piece of software. When parsing the product key, I can check what type of product someone has (trial/full version, etc). We'd like users to be able to upgrade from free versions to paid versions of the code library and want trial versions that check if the product key is valid.

The trial version doesn't include all the code of the full version. The product keys are checked using an algorithm (it doesn't use web services or something like that), so that can be done very fast.

There are two sub-problems here:

  • Going from a trial version to a full version. I'm thinking about solving this with MSI upgrades, but am not sure how this can be achieved. (using WIX)
  • Making a trial build that includes extra code for checking if it's a trial version. I know this is possible using #if statements, but am wondering if it's also possible to change a DLL - more precisely: add static constructors for every type that check for a trial version.

My questions are: is this the right way to do this? And if so, are there standard ways (or tools) of achieving this kind of functionality? And if not, what is the best way of doing this?

update

Thanks for all the great answers so far. I think it's time to explain more of the context judging from the answers - fortunately it seems like we're more or less on the right track.

For our products it makes sense to install it on a private (non-internet connected) VLAN, so that makes product keys the way to go... and I really don't want to go to the trouble of 'phone activation' like Microsoft did :-). Therefore, as suggested, I'm currently using product keys that encrypt a bit of data, which is decrypted to see if the key is valid for the given product. Sure, I can be a victim of software piracy, but I suppose we'll consider that as a "marketing instrument" for now.

As suggested in one of the answers, I'm currently generating multiple MSI files: One for x64 and one for x86. And then one for a trial, a full version and a blueprint version. That makes a total of 6 MSI files (pfff). During the process obfuscation, strong naming and code signing takes place. The blueprint version includes the full source code.

I am rather concerned about trial builds. Sure, I can add some checks here and there, but I'd rather just put checks in every class, automatically... I've thought of copying all the CS files and adding / changing static c'tors (f.ex. using the cecil or nrefactory.. or just a couple of regex...). It just seems to me that I'm not the only guy that wants this. There has to be a better way, right?

Further, I really don't want to think about updates and upgrades, because it seems like reinventing the MSI wheel. From a trial to a full version seems to me like an MSI upgrade, just like a full version to a blueprint version feels like an MSI upgrade. However, combined with normal upgrades I'm concerned if this is possible at all?

atlaste
  • 30,418
  • 3
  • 57
  • 87

4 Answers4

1

Try Rhino Licensing (open source)

Tie the generated license to individual's machine using machine unique identifier such as computer SID, HDD serial number, CPU Identifier, etc. Use float licenses and check the availability of the license on the server at runtime. Rhino Licensing provides necessary infrastructure for you to achieve this. Rhino Licensing can also include custom data in the license file so you have the ability to easily store your customer information in the license file. With all these said, having a licensing scheme on your application does not mean you are completely safe. Hackers and crackers try to reverse engineer your application code to see how the licensing is done, so you make look into other security concerns and it is advised to use an obfuscation tool to make this harder for malicious users.

Jakub Konecki
  • 45,581
  • 7
  • 87
  • 126
  • Thanks! While it's not something I can use within my business context, this does seem like a good solution for handling most license keys. Certainly something I'll remember. – atlaste Feb 05 '13 at 23:00
0

You can just release the full version and disable some functionality for the trial version. When the user upgrades his license, he need not download anything. Don't worry too much about cracks and keygens. If someone makes a crack for your software, it means that you're successful, and you're already making a lot of money.

simoncpu
  • 578
  • 5
  • 9
  • Sorry, but that's not an option here, because the full version means "blueprint version", e.g. including source code. I'm not too concerned with cracks etc, but do feel like I should make it a bit difficult. – atlaste Feb 04 '13 at 21:53
0

To solve this issue with our application we used a Guid and selected set points in the Guid to let us know what version they are using. After that we just installed the full system and used bool values when the software starts up.

  • Yeah, while I appreciate the thought, that's definitely *not* how I want it... it'll probably take me just under 5 minutes to hack that piece of software... Sure, nothing is fool proof, but at least I really don't want to keep the door open. Also, I'm providing the source code in one of the versions, which is not something you can easily "disable". – atlaste Feb 05 '13 at 22:56
-1

Mixing trial and production environments can get messy. If possible make two separate installers for each.

There are some prerequisites to making your protection as strong as possible:

  • Obfuscate your code with the best obfuscator you can buy.
  • Spread the checks for valid signatures in several key places in your code, don't check just once at startup.
  • Learn about .NET crypto APIs, such as asymmetric encryption and signatures
  • Keep in mind that generated product keys can be reverse-engineered, mostly a concern if your app is high-volume or high-value

Scenario with Activation:

Upon installation or within some time after it you can require users to "Activate" your product. When activating you can generate a plaintext XML file with some components identifying the system - CPU ID, HDD ID, domain name, user name etc. Send said XML to your web service.

The service can then append a random tag, hash the data together and have it signed with a private key and return the signature and the random tag.

Your client software will have the matching public key built-in and will store the random tag and signature locally.

To check if the license file is valid the app will hash the plaintext data and random tag together and check if the signature matches that hash.

This gives you a very reliable way for determining if the instance your client is running is purchased and pretty much guarantees they can't just modify the XML itself.

Scenario without Activation:

Upon purchase send a file with some customer data, such as their name, email, phone etc - make it somewhat personal so the user will be reluctant to share their own license file.

Include a random tag and hash all the data together. Sign the hash and store it in the file as well.

The user will have to save the file at a predetermined location or paste it inside a textbox in your app.

Community
  • 1
  • 1
Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
  • Thanks, you have some interesting markers. I added more business context to my question to give you a bit more understanding of the situation. It seems your answer is along the same line as I was working -- but I'm still struggling with the trial and the different installer versions (you really don't want a trial and a full version on a system :-) ). – atlaste Feb 04 '13 at 22:33
  • Several ways to deal with that: (1) just one installer and make your code dynamically check the key and if it's not there - switch to trial UX - this could burden trial users; (2) set all IDs and folder targets in both trial and full installers to be the same, make the full installer always a version above the the trial - this is a hack and may be difficult to maintain long term; (3) one installer with dynamic rules what to install - this is difficult to do right and to maintain long term. All approaches I see have issues, pick your poison :) – Sten Petrov Feb 05 '13 at 15:59
  • Thanks. After considering the alternatives, adding a build variable and adding manual '#if' code seemed like the best way to create a trial installer. And since poison sucks... I thought about a (4): inverse prerequisite check/auto uninstall of 'lesser versions'. With WIX you can check if certain (install) dependencies are met (and take action), so you can probably check if they are *not* met as well... that shouldn't be too hard, going to try this soon - at least it feels to me like it's less painful then what you suggest... Either way I definitely prefer to do things right for the long term. – atlaste Feb 05 '13 at 22:52
  • How is this proposed system not automatically susceptible to chosen-plaintext attacks? – Eric Lippert Feb 12 '13 at 21:37
  • @EricLippert how do you suggest you attack my scheme? – Sten Petrov Feb 12 '13 at 21:40
  • 1
    Call the plaintext a "token". (1) The "token" sent is actually a captured ciphertext signed with your public key. Your service is now a "decrypt captured encrypted messages" service. (2) The attacker sends specially-crafted token1 to the service and obtains signed token1. From that, the attacker may deduce what the service would have said for token2. Now the signed and unsigned token2 can be distributed to other attackers, but the service has never seen token2. These are just two common chosen-plaintext attacks; cryptosystems assume that only the key holder uses the private key! – Eric Lippert Feb 12 '13 at 21:54
  • @EricLippert you need to refresh your public key cryptography. Knowing `token1` and `RSASign(token1, privateKey)` values does NOT allow you to deduce the value of `RSASign(token2, privateKey)` without the `privateKey` itself. The `privateKey` is never distributed and kept secret on the activation server, where did you get the idea that it's shared?? – Sten Petrov Feb 12 '13 at 22:21
  • @EricLippert if RSA was broken the way you suggest it is we're in much bigger of trouble than some activation key exploits (: – Sten Petrov Feb 12 '13 at 22:23
  • 1
    It most certainly does! **If you can craft the token1 with knowledge of unencrypted token2 then the information returned by the encryption of token1 with the private key allows you to deduce the encrypted token2**. This is a standard attack vector, and it is well known that RSA is not resistant to it. I think the person who needs to refresh their knowledge of RSA attacks is you. **RSA is only secure when the owner of the private key chooses the plaintext**. It was not **designed** to be secure in the face of attacker-chosen plaintexts and it is **not**. – Eric Lippert Feb 13 '13 at 00:30
  • 1
    Start by reading this paper. http://www.dtc.umn.edu/~odlyzko/doc/arch/rsa.attack.pdf – Eric Lippert Feb 13 '13 at 00:44
  • @EricLippert thanks for the useful heads-up, I should have remembered this. But it does raise the question: how would you implement licensing in the (offline) context that I describe? – atlaste Feb 13 '13 at 09:30
  • 1
    I'm an expert on programming language design, not licensing schemes (or crypto) so i wouldnt care to venture an answer outside my area of expertise. I am skeptical of the value of licensing schemes in general: they seem to me to be a high tech solution to a legal and social problem, rather than a technical problem. – Eric Lippert Feb 13 '13 at 14:25
  • @EricLippert +1, I read the article, excellent source of new info, thanks! It, however, does not have an impact on this licensing scheme. The scheme relies on the app being obfuscated. If it's not obfuscated there will be even easier hacks, such as runtime code injection. If it is obfuscated the attacker does not have the public key and does not have access to failed decryption messages. In order to gain access to them the attacker has to de-obfuscate the code, which would mean he can bypass the activation altogether. – Sten Petrov Feb 13 '13 at 15:40
  • 1
    @EricLippert Taking your input the scheme can be hardened by first requesting a random token from the licensing server that's included in the device and user data that's hashed and signed. This way the attacker couldn't choose their plaintext. – Sten Petrov Feb 13 '13 at 15:42
  • Right; now you're onto something that is much harder to craft a plaintext attack against. If you have to take input from an untrusted source then (1) first check it to see if it looks like it is an attempt at an attack; if it is, then fail to a secure mode, otherwise (2) salt it, (3) hash it, and (4) use an implementation of RSA that has been tweaked to be more resistant to chosen plaintext attacks. **Defense in depth is key**. And if you think obfuscation does more than slow an attacker down by a few minutes, think again. Locking your car doesn't prevent it from being stolen. – Eric Lippert Feb 13 '13 at 15:49
  • 1
    @EricLippert nothing is 100% secure, it all boils down to cost/benefit ratio. Obfuscation can slow an attacker significantly and if the benefit isn't too big they will likely give up. If your software costs $100 and it takes 3 months for a highly skilled person to crack it your investment will be worth it - if your software is high-volume you can probably afford to push minor updates every couple months, each time with a new key. If it's not high volume hacker of that skill likely won't be interested in wasting his time – Sten Petrov Feb 13 '13 at 16:15
  • @StenPetrov Nothing is 100% secure, because people are involved. But from a purely technical point of view, you *can* make 100% secure system (e.g. [OTP](http://en.wikipedia.org/wiki/One-time_pad)). And obfuscation doesn't help you with that. – svick Feb 13 '13 at 17:15
  • 1
    @EricLippert I think your concerns regarding chosen-plaintext only apply to textbook RSA which should not be used. I very much doubt any such attack works on RSA with proper padding(PSS or even the old PKCS1-v1_5). | But even if it were, there are far fewer people who could break the crypto, as opposed to those who simply patch out the licensing check. – CodesInChaos Feb 13 '13 at 20:41
  • Sure it's easier to just disassembly, add some branches and reassemble again. I also thought about checking code signing when pushing an update or blocking the user all together... And while I agree with Eric that it's a legal / social problem, unfortunately so is my mortgage... The catch-22 is that I'm also a software expert and thus feel like I should add value there instead of implementing stupid licensing schemes and installers, but on the other end also want to feed myself and my wife. So, I'm going to be pragmatic. Thanks all for the great in-depth advice, in particular Sten and Erik. – atlaste Feb 19 '13 at 08:23