Playing with key generators
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:
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:
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?
Comments
My immediate reaction on reading the title was "For shame, after calling out someone else for doing the same thing!"
https://ayende.com/blog/172771/how-to-ensure-that-you-wont-get-hired-quickly
:-)
I'd suggest not using a column format to send to users. As they may try to paste the key in column by column order, instead of row by row order. I imagine someone parsing it in Excel to get it in the "right" format.
Users see (moderately) big and ugly, and think secure...
We think on Friday evening your family has more rights on you )
I like the base64 format more than the hex column format.
Why did you switched to the hex formatting?
// Ryan
The JSON w/signature is cool. Is there a standardized way to do that? I ask because I wrote a library that signs and verifies JSON exactly like that but I couldn't find anyone else on the internet doing the same thing.
I agree with @Ryan Heath. The SMAZ + Base64 version is shorter, in line with other key generators, and would suffice - skip the HEX.
I think you forgot to add T00:00:00.0000000 to the terms table. :)
Other than that I think the base64 looks better than the hex.
Do you think about JWT? It will be slightly longer (without signature string 972 vs 837 of yours Base64 version).
But you can do two things. 1) encode payload as you want. I minified payload in JWT using MessagePack in one of my projects and reduced message from ~600 to ~180 bytes. To get information back just add the type of license in header, and you are free to change anything inside payload.
2) Change the way how sign it (HMAC, RSA, ECC), or even encrypt. By default you can use signature HS256 (HMAC Sha256).
And as a bonus - personal licenses. Client generates pub/private keys in license request sends it public key - the rest is easy.
That is how your license looks like: // test in on jwt.io
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaWQiOiAiY2Q2ZmZmMDItMmFmZi00ZmFlLWJjNzYtOGFiZjFlNjczZDNiIiwNCiAgImV4cGlyYXRpb24iOiAiMjAxNy0wMS0xN1QwMDowMDowMC4wMDAwMDAwIiwNCiAgInR5cGUiOiAiU3Vic2NyaXB0aW9uIiwNCiAgInZlcnNpb24iOiAiMy4wIiwNCiAgIm1heFJhbVV0aWxpemF0aW9uIjogIjEyODg0OTAxODg4IiwNCiAgIm1heFBhcmFsbGVsaXNtIjogIjYiLA0KICAiYWxsb3dXaW5kb3dzQ2x1c3RlcmluZyI6ICJmYWxzZSIsDQogICJPRU0iOiAiZmFsc2UiLA0KICAibnVtYmVyT2ZEYXRhYmFzZXMiOiAidW5saW1pdGVkIiwNCiAgImZpcHMiOiAiZmFsc2UiLA0KICAicGVyaW9kaWNCYWNrdXAiOiAidHJ1ZSIsDQogICJxdW90YXMiOiAiZmFsc2UiLA0KICAiYXV0aG9yaXphdGlvbiI6ICJ0cnVlIiwNCiAgImRvY3VtZW50RXhwaXJhdGlvbiI6ICJ0cnVlIiwNCiAgInJlcGxpY2F0aW9uIjogInRydWUiLA0KICAidmVyc2lvbmluZyI6ICJ0cnVlIiwNCiAgIm1heFNpemVJbk1iIjogInVubGltaXRlZCIsDQogICJyYXZlbmZzIjogInRydWUiLA0KICAiZW5jcnlwdGlvbiI6ICJ0cnVlIiwNCiAgImNvbXByZXNzaW9uIjogImZhbHNlIiwNCiAgInVwZGF0ZXNFeHBpcmF0aW9uIjogIjIwMTctSmFuLTE3IiwNCiAgIm5hbWUiOiAiSGliZXJuYXRpbmcgUmhpbm9zIg0KfQ.ICmXK04wZBwUwg8vi3mwiqYEZuH8GUBjEhan-GsjS-g
My take on the encoding:<br>
<br> Mostly because I think this is going too far:<br>
Sergio, That is pretty much the same as my initial JSON + sig version, as far as I can see.
Josh, I don' think that I'm following here
Oren, I thought hexadecimal was a bit of a missed opportunity and it would be cooler with a quick:<br><br>
That way there’s an easter egg for the cryptanalalsts out there :)
I'd go with the base64-encoded variant, but I'd bracket it with "-----BEGIN LICENSE-----" / "-----END LICENSE-----" lines, similar to a PEM-encoded certificate file. It makes it easier to see the beginning and end of the license key in an email (e.g.).
I'd prefer receiving the base64 approach with a BEGIN LICENSE/END LICENSE lines like Roger suggested. It is pretty common to see those that you just paste into an app and the boundaries make it clear (though I assume they're just stripped by the program after begin pasted)
Comment preview