Changing keyfile format of OnlineUnlockStatus

#1

Is there a way to change the format of the keyfile used by the OnlineUnlockStatus class, without re-writing the whole class?

0 Likes

#2

Upon closer inspection, it looks like the format of the keyfile is determined by struct KeyFileUtils within the juce_OnlineUnlockStatus.cpp file.

And, therefore the answer to my initial question seems to be “no”.

Now, the next question in my mind is, should I just rewrite the .cpp file and attempt to leave the juce_OnlineUnlockStatus.h untouched?

Obviously anything’s possible, but I’m wondering more in terms of forking JUCE and being able to merge future changes back in to my copy. If I leave the header file intact, then at least the class’s interface would remain compatible with future updates, right? Just looking for advice to minimize headaches…

0 Likes

#3

We could add hooks to save it in some other format, but the KeyFileUtils class doesn’t actually write anything to disk, it just gives you the content, so you could easily just convert the file into your own format before you save/load it, without touching the juce code.

Why would you need to change it anyway? It’s just a lump of text, and the classes are going to need all the items it contains, so how would it help to store it differently? In its current form you could just store it inside your own XML, JSON or binary wrapper format with no hassle.

0 Likes

#4

Sorry if I wasn’t being precise enough in my question. I didn’t actually mean the file format (txt, xml, whatever) of the keyfile as stored to disk. I meant the contents of the keyfile, as determined by KeyFileUtils::createKeyFileContent.

Currently the keyfile contents are created from the arguments for: appName, userEmail, userName, and machineNumbers, plus a timestamp added in the createKeyFileContent method.

My point was, what if I wanted to store additional information in the keyfile? For example, the license ID number that was used to authorize the software install. (Otherwise, for customers who have purchased multiple copies of a license, there’s no association between a specific license ID and the “unlocked” status of the software on a particular system.)

0 Likes

#5

Ah right - yes, that’d be a more meaningful use-case! I guess for that sort of thing the best approach would be to create some overridable method for generating custom XML instead of what’s currently there.

I guess you could either just hack the code yourself and keep it in a fork, or send us a suggested change which would let you hook in what you need, which we could review and maybe add to the class.

0 Likes

#6

OK, cool, I’ll see what I come up with and if it seems worth sharing.

Besides storing additional fields in the keyfile, another change I was looking into was expanding OnlineUnlockStatus to optionally use RSA signature verification, rather than only offering the RSA decryption approach that it currently uses.

My approach to license keyfiles has been to publish them (i.e. to the user) as XML files with all the fields easily human readable. Then an additional XML element gets added to the file, which is a signature to verify the rest of the contents. Hence my interest in using RSA signatures instead of RSA encryption.

Benefits to using a readable XML file with an RSA signature include:

  1. In cases where you cannot tie a keyfile to a particular system (via the Machine ID), a keyfile can then be easily passed around and shared by any number of users. However, including the purchaser’s email address as a readable element in the XML file discourages this sharing, by making the source of the original keyfile known. This deterrent would not happen however with an encrypted keyfile that just looks like a string of gibberish.

  2. If you are including an expiration time in a license, then the user can easily read what that date is if they need to know.

I’m definitely not an expert in the field, but that’s just my take from my experience.

That said, unless you trying to hide information from the user, I don’t see any benefit to encrypting keyfiles rather than signing them.

0 Likes

#7

Also on that note - any chance of the RSAKey class being expanded to handle RSA signing and verification?

0 Likes

#8

I don’t think that’d offer anything that the current format doesn’t… IIRC we put both the plain text content and the encrypted version in the same file, so it can be read.

And sure, adding RSA signing is a good FR but probably would be pretty low on our priority list, I’m afraid

0 Likes

#9

What it offers is plain text that cannot be altered without invalidating the keyfile.

The first benefit I listed above (for signing rather than encrypting) was discouraging a user from sharing a keyfile by having their email address readable in it. If you simply append a plaintext “comment” with their email address to the encrypted key, they could just delete the plaintext part before passing around their keyfile, and it would still work.

Some may view that as a “fringe case” sort of benefit. In my view, in the absence of using Machine IDs to tie an authorization to a particular system, then psychological pressure is all you have to encourage users to do the right thing. Being publicly accountable, with their identity exposed if they share their keyfile, is one way to do that.

0 Likes

#10

Like I said, that has the same outcome as our format. Changing the text in our keyfiles also invalidates it, because the reader will choke on it if the plain text doesn’t match the encrypted version.

The reason our format is encrypted version + unencrypted version rather than plain text + hash is that it makes it harder to crack. If security is based on checking a hash, then a cracker only has to remove the branch instruction that fails if a check fails, and then it’ll accept any key file. However with our version, the actual unlock credentials come from the encrpyted block, and a hacker doesn’t have the private key to create it. So cracking it would involve either replacing the entire decoding function, or at least replacing the obfuscated RSA key, which are both much harder.

0 Likes

#11

With all due respect, that is not what I have observed while learning to use OnlineUnlockStatus.

The saveState method gets passed a single Base64 encoded string. That’s what I then save to disk in a keyfile. When I quit and re-load the plug-in host, it fires getState and reads that string back in, authorizing the plug-in again.

In other words, the keyfile is solely the encrypted string, no plaintext – and the OnlineUnlockStatus class has no problem unlocking the plug-in with just that.

Am I missing something essential with how that is supposed to work?

0 Likes

#12

Also, I want to be clear that I’m not advocating a plain text + hash function validation. I’m talking about plain text with an RSA signature, requiring a public key to verify.

Again, sorry if I’m missing your point.

0 Likes

#13

I could be mis-remembering but I’m pretty sure we distribute e.g. tracktion keyfiles which contain the plaint text as well. Obviously like you said, it’s very hard to see what’s in a file otherwise. Maybe that’s something we do in tracktion in addition to the basic unlocker classes just generating the encrypted bit.

Yes, you are!

When I said “plain text + hash” then I was talking about an RSA hash signature. My point (which I thought I explained pretty clearly!) was that if you load the user details from the plain-text, then to crack it is easy, regardless of the hash function used, because all a hacker has to do is change the branch instruction in your app which decides whether the hash was correct, and then any keyfile will work, with any content. My point was that by loading all the details from the encrypted block, then it’s much harder to crack.

0 Likes

#14

I can’t speak to how Tracktion keyfiles are distributed, but a keyfile produced from the OnlineUnlockStatus::saveState method does not contain human-readable plaintext.

I had said “sorry if I’m missing your point” to be polite, and to acknowledge the possibility that perhaps we were talking apples and oranges when saying “RSA signature” and “hash”. But ok.

I will have to leave the finer points of cracking plug-ins to the pros, as that is not my area of expertise. However, if you’re talking about a cracker going in and manipulating branch instructions (rather than, say, producing their own keygen), then of course anything’s possible, whichever cryptographic method was used to secure the license file.

With your encryption approach, once you decrypt the license details, then as you say you have your “actual unlock credentials”… but those are then checked with branching instructions. You say that “by loading all the details from the encrypted block, then it’s much harder to crack” - why? By virtue of the fact that there are multiple branching instructions, to check those multiple credentials? (e.g. one each to check for matching product ID and an allowed Machine ID)

If instead you were using an RSA signature scheme, there’s no reason why the check of a valid signature couldn’t be repeated multiple times, or be obscured somewhat with dummyResult shenanigans like is done in OnlineUnlockStatus::applyKeyFile.

Jules, I hear that you have a preference for using encrypted license files, and I’m not trying to change your mind about that. For the reasons I’ve mentioned previously here (chiefly, user accountability in the absence of using Machine IDs in license files), I have a preference for using signed license files with readable plaintext XML. And so I do wish that the JUCE OnlineUnlockStatus and RSAKey classes could perform RSA signing and verification duties, because then I wouldn’t have to bring in an outside library for that. It would be less work for me and, most likely, a tighter and more secure implementation than I would produce working solo.

0 Likes

#15

Sure, adding signing to the RSAKey class is a valid FR (though sadly unlikely to make it very high up our priority list)

But my point is just that although a signed key file would look a bit different, it would add nothing whatsoever in terms of security, and if your aim is for users to be able to see who’s file it is, then just chucking the plain-text on the end of the current file format would do the job perfectly well. To a reader it doesn’t matter whether the block of hex numbers in the file is a signature or an encrypted chunk.

Yes, obviously to slow down pirates you need to re-check things in multiple places - that’s true regardless of anything else. But it does help to make them work harder if a chunk essential data is decrypted using a public key rather than just pulled in from a plain-text file. Having said that, they’ll crack it all anyway if they want to!

0 Likes

#16

There’s the rub. Yes, I could just chuck the user’s name and email as plaintext on the end of the keyfile (in a “courtesy to the user” sense).

However, because that plaintext is not cryptographically secured, there’s nothing to stop them from just deleting their name and email off the keyfile. The license validation would still happen, because it’s only checking the encrypted chunk.

And then what the user would have is a keyfile with no human-readable identification in it, that will work anywhere (again, this is for a system that’s not doing Machine ID checking). The perception that gives is that the keyfile can be shared among friends, or even published online, with no repercussions.

A signed license file might not add anything in terms of digital security, but it does keep the owner of the file immediately identifiable, and thereby perhaps encourages paying customers to do the right thing and keep their license files to themselves. So in that sense I think of it as adding psychological security.

I have no numbers or research to prove that this actually makes a whit of difference, just my own experiences and observations. I have seen many times people who are generally honest in their day-to-day lives be willing to share software license keys among acquaintances. Maybe having their name attached to the act of sharing those keys would alter their behavior.

And yes, I sadly agree that they’ll crack it all anyway if they want to. The ongoing goal therefore is to make it more convenient for the user to use the legit software rather than a cracked version… and hence the struggle to find a balance between a licensing scheme that is (somewhat) secure without being onerous or intrusive.

0 Likes

#17

Well, it’s up to your code, if you want to compare the decrypted license with the “courtesy version”…

Something nobody mentioned so far, are you aware, that you cannot embed the signature inside the plain text license file, since that would change the content and therefore invalidate your signature?
You will have to define a way, that excludes the signature from the signed text.

1 Like

#18

Seems pretty obvious, and it’s what I’ve done in my key file implementation using an XML file with plain text versions of the data that must match those in the encrypted section, something like this:

<keyfile>
  <name>Joe Bloggs</name>
  <email>joe@bloggs.com</email>
  <key>{RSA encrypted string}</key>
</keyfile>

RSA encrypted string looks like Joe Bloggs;joe@bloggs.com;pluginName and those things must all match up to be considered valid.

If you spend more than a day or two at most trying to implement copy protection, it’s a waste of your time that could be better used imo, because they will get cracked, but if you at least stop casual pirates it’s a small win.

0 Likes

#19

Yes, anything’s possible… I was just trying to get it all done with OnlineUnlockStatus and JUCE’s RSAKey class, which is not possible.

As for embedding a signature, in the past I have done essentially what @richie is describing - put together an XML file with all the license data fields, sign that, and then add the signature as one last XML element. On the verification side, just pull the signature element out first, and then verify the contents of the remaining XML file with the signature.

However, @richie is saying he encrypts the XML contents, which, as it turns out, is different than signing them. The RSA algorithm is in theory the same for both, but the real-world implementations are different. So, while it will basically function the same, signing with an encryption algorithm is vulnerable to more cryptographic attacks.

I am by no means a crypto expert, but if you’d like to hear from some folks who are, here you go:

And an explanation here that concludes:

In the abstract world of textbooks, RSA signing and RSA decryption do turn out to be the same thing. In the real world of implementations, they are not. So don’t ever use a real-world implementation of RSA decryption to compute RSA signatures. In the best case, your implementation will break in a way that you notice. In the worst case, you will introduce a vulnerability that an attacker could exploit.

0 Likes

#20

So if I understood correctly, to make my key files less open to cryptographic attack I should do something like this:

<keyfile>
  <keydata>
    <name>Joe Bloggs</name>
    <email>joe@bloggs.com</email>
    <key>{RSA encrypted string}</key>
  </keydata>
  <signature>{RSA encryption of <keydata> padded to a uniform length with random characters}</signature>
</keyfile>

I’m not sure I’d actually bother, given my earlier comment about wasting time I could better use building plugins, but if I’m bored one day I may do this if it’s going to make things slightly more secure. :joy:

edit: I really don’t understand crypto at all, but my gut feels that this wouldn’t really add any benefit somehow :joy:

0 Likes