back

by gnabgib·4y ago·view on hn ↗
It's a weird situation, but it's very obvious with HC1 and SHC cards.

It starts with JWT. JSON is a human readable format (in utf-8), if humans don't need to read, the data could be binary, and the format could be exact. JSON isn't an exact spec, which is mostly inherited from JavaScript (there's no such thing as an integer only floating point, so 1e3==1000==1000.0==1.00e3 in human-readable form, as a stored number they are identical). Then there's differences in white-space (new lines, indentation) - although this could likely be overcome with convention. Because of this the JWT creators said instead of signing the data, we'll sign the exact representation in the payload - but of course with white-space and formatting variance (including a deserialize/serialize loops changing representations, or - in the case of bearer tokens, the HTTP spec allowing newlines/white space to be inserted at the protocol level) they had to encode it as non-human readable (base64). Now everyone agrees you're signing that exact Base64 representation of the JSON object. But! We've build a(n arguably verbose) human readable format that isn't readable by humans.

The SHC spec (common in North America) actually holds a JWT that's signed by an elliptic curve private key. You can validate the signature with a public key. The public/private choice here is great, the JWT is terrible.. they've doubled down on the mistakes. Further to keep the QR smallish, they zipped the payload portion (which is supported by JWT - this is done before the base64 stage), and use only the minimum QR resilience setting (which is fine if it's on a screen, if it's printed this may lead to reading problems). Now we have human readable (JSON) compressed in machine readable (deflate) in machine readable (base 64) in machine readable (QR) - for machine reading purposes. They didn't even trim the fluff (every SHC begins with 56 because.. you guessed it, the `{` character), or use sensible choices (they don't use IssuedAt/iat, but NotBefore/nbf to indicate the generation date). Anyway, SHC (reasonably) noticed because of the (mostly) base64 encoding the character set is only 64 characters (6 bits) which doesn't use the ASCII space (7 bits) very well, so they store the first 'shc://' in ASCII and the rest is a number (there are three modes in QR: ASCII, binary, numeric - the density loosely matches binary representations - a numeric digit (0-9) takes 4 bits, ascii char takes 7 and binary takes 8).

ASCII doesn't support the world very well, UTF8 isn't supported by QR (except as binary).

In the SHC case, because it's signing a specific format/output of the JSON data, it doesn't have the white-space formatting concerns that JWTs have to overcome. If they wanted to stick with a JWT like format (JSON object), they could have skipped the base64 before sign step, at which point they might as well get rid of the header (we're no longer to JWT spec), deflate the message to be signed, and put the signature after the deflated message. All the same data, less of the overhead, and better use of the binary space.

1 comments
It doesn’t make any sense to me why they gzipped the data. I’ve looked at a few real world DGCs and the “compressed” output was actually larger than the original data! Just adds more complexity for no purpose…