Playing with key generators

time to read 5 min | 867 words

For the past 6 years or so, Hibernating Rhinos is using a licensing component that I wrote after being burned by trying to use a commercial licensing component. This licensing scheme is based around signed XML files that we send to customers.

It works, but it has a few issues. Sending files to customers turn out to be incredibly tricky. A lot of companies out there will flat out reject any email that has an attachment. So we ended up uploading the files to S3, and sending them a link to it. Dealing with files in this manner also means that there is a lot of relatively manual steps (take the file, rename it, place it in this dir, etc). With all the attendant issues that they entail.

So we want something simpler.

Here is a sample of the kind of information that I need to pass in the license:

image

Note that this is a pure property bag, no structure beyond that.

An obvious solution for that is to throw it in JSON, and add a public key signature. This would look like this:

{
  "id": "cd6fff02-2aff-4fae-bc76-8abf1e673d3b",
  "expiration": "2017-01-17T00:00:00.0000000",
  "type": "Subscription",
  "version": "3.0",
  "maxRamUtilization": "12884901888",
  "maxParallelism": "6",
  "allowWindowsClustering": "false",
  "OEM": "false",
  "numberOfDatabases": "unlimited",
  "fips": "false",
  "periodicBackup": "true",
  "quotas": "false",
  "authorization": "true",
  "documentExpiration": "true",
  "replication": "true",
  "versioning": "true",
  "maxSizeInMb": "unlimited",
  "ravenfs": "true",
  "encryption": "true",
  "compression": "false",
  "updatesExpiration": "2017-Jan-17",
  "name": "Hibernating Rhinos",
  "Signature": "XorRcQEekjOOARwJwjK5Oi7QedKmXKdQWmO++DvrqBSEUMPqVX4Ahg=="
}

This has the advantage of being simple, text friendly, easy to work send over email, paste, etc. It is also human readable (which means that it is easy for a customer to look at the license and see what is specified.

But it also means that a customer might try to change it (you'll be surprised how often that happens), and be surprised when this fails. Other common issues include only pasting the "relevant" parts of the license, or pasting invalid JSON definition because of email client issues. And that doesn't include the journey some licenses go through. (They get pasted into Word, which adds smart quotes, into various systems, get sent around on various chat programs, etc). If you have a customer name that include Unicode, it is easy to lose that information, which will result in invalid license (won't match the signature).

There is also another issue, of professionalism. Not so much how you act, but how you appear to act. And something like the license above doesn't feel right to certain people. It is a deviation from the common practice in the industry, and can send a message about not caring too much about this. Which lead to wondering whatever you care about the rest of the application…

Anyway, I'm not sure that I like it, for all of those reasons. A simple way to avoid this is to just BASE64 the whole string.

What I want to have is something like this:

eyJpZCI6ImNkNmZmZjAyLTJhZmYtNGZhZS1iYzc2LThhYmYxZ
TY3M2QzYiIsImV4cGlyYXRpb24iOiIyMDE3LTAxLTE3VDAwOj
AwOjAwLjAwMDAwMDAiLCJ0eXBlIjoiU3Vic2NyaXB0aW9uIiw
idmVyc2lvbiI6IjMuMCIsIm1heFJhbVV0aWxpemF0aW9uIjoi
MTI4ODQ5MDE4ODgiLCJtYXhQYXJhbGxlbGlzbSI6IjYiLCJhb
Gxvd1dpbmRvd3NDbHVzdGVyaW5nIjoiZmFsc2UiLCJPRU0iOi
JmYWxzZSIsIm51bWJlck9mRGF0YWJhc2VzIjoidW5saW1pdGV
kIiwiZmlwcyI6ImZhbHNlIiwicGVyaW9kaWNCYWNrdXAiOiJ0
cnVlIiwicXVvdGFzIjoiZmFsc2UiLCJhdXRob3JpemF0aW9uI
joidHJ1ZSIsImRvY3VtZW50RXhwaXJhdGlvbiI6InRydWUiLC
JyZXBsaWNhdGlvbiI6InRydWUiLCJ2ZXJzaW9uaW5nIjoidHJ
1ZSIsIm1heFNpemVJbk1iIjoidW5saW1pdGVkIiwicmF2ZW5m
cyI6InRydWUiLCJlbmNyeXB0aW9uIjoidHJ1ZSIsImNvbXByZ
XNzaW9uIjoiZmFsc2UiLCJ1cGRhdGVzRXhwaXJhdGlvbiI6Ij
IwMTctSmFuLTE3IiwibmFtZSI6IkhpYmVybmF0aW5nIFJoaW5
vcyIsIlNpZ25hdHVyZSI6IlhvclJjUUVla2pPT0FSd0p3aks1
T2k3UWVkS21YS2RRV21PKytEdnJxQlNFVU1QcVZYNEFoZz09I
n0=

Note that this is just the same thing as above, with Base64 encoding. This is a bit big, and it gives me a chance to play with the Smaz library I made. The nice thing about the refactoring I made there is that we can provide our own custom term table.

In this case, the term table looks like this:

image

And with that, still using Base64, we get:

AXICAAeTBQoLKA0NDWgsJAgNDSwmDQgMLAkKKSgsKggJDSMMK
CklCyUJBgwfFxAZCBsQFhUFhyxnLHeVVCIiAyIiAyIilS4iIi
IiIiIiBpIFjwZEB0oFNwZLBSgGTIxNjE4FSQZajE+NUoxTjVS
NVY1WjVcFSQZZjVCNUYxYBYcsWyx3BpEFlUgQCQwZFQgbEBUO
liBSDxAVFhoGlVMQDhUIGxwZDAULFyYLlSsYJyUYC5VZJBUOl
U8hmFBRUkEXlVcrJxSVUSIQEZZDRRQplUEWECIhllBYEiEgIp
ZWTyUhlkpUGJZBRh6WPT0HAWzteBm/nZAfi4aI9mkbVowBmGV
PC0S0fXaHv1MUodMQdsS6TuuvmlU=

This is much smaller (about 30%). And it has the property that it won't be easily decoded by just throwing it into a Base64 decoder.

I decided to use plain hex encoding, and pretty format it, which gave me:

0172020007 93050A0B28 0D0D0D682C 24080D0D2C
260D080C2C 090A29282C 2A08090D23 0C2829250B
2509060C1F 171019081B 1016150587 2C672C7795
5422220322 2203222295 2E22222222 2222220692
058F064407 4A0537064B 0528064C8C 4D8C4E0549
065A8C4F8D 528C538D54 8D558D568D 5705490659
8D508D518C 5805872C5B 2C77069105 954810090C
1915081B10 150E962052 0F1015161A 069553100E
15081B1C19 0C05954511 740C1C984D 4B4641270B
95560F1608 209841492F 4108962B58 0B9556120C
95590C954C 2195441695 5623954C29 2695482009
1D1C97514D 2B1117974E 492B150E96 3D3D070157
A9E859DD06 1EE0EC6210 674ED4CA88 C78FC61D20
B1650BF992 978871264B 57994E0CF3 EA99BFE9G1

This make it look much better, I think. Even if it almost double the amount of space we take (we are still smaller than the original JSON, though). Mostly this is me playing around on Friday's evening, to be honest. I needed something small to play with that had immediate feedback, so I started playing with this.

What do you think?