JSON Web Token Right Answers
There was a craze a few years ago for publishing “cryptographic right answers” articles. These articles didn’t tell you anything much about how cryptography works, they just tried to give simple recommendations for what to do in different scenarios: you need to encrypt some data at rest? Use XYZ algorithm with a 256-bit key. Several iterations of this article were published by prominent cryptography engineers, and then there’s a bunch of gists on GitHub with some more dubious variations on the theme.
In this newsletter edition I’m going to give you my take on cryptographic right answers, but with the twist that I’m going to confine myself to algorithms usable for JSON Web Tokens. This is a topic I get asked about all the time.
Now, those of you who do know something about cryptography may be screaming at your computer right now: “But Neil, JWTs are never the right answer! JWTs are the definition of cryptographic wrong answers! If JWT is the answer, you’re asking the wrong question!” And so on. And I mostly agree with all that! JWTs are one of those standards that seem simple and clear to begin with but turn out to be incredibly easy to mess up in practice. I have a huge list of less-talked-about things that are wrong with JWTs. However, they are also for better or worse incredibly widespread at this point and probably not going anywhere. For example, OpenID Connect mandates the use of JWTs, which ensures its continued usage for years to come. (People still use SAML, and believe me, XML crypto is much worse than JWTs).
If you don’t need to use JWTs then please don’t use them. There are better alternatives in many cases. But if you do need to use JWTs, for whatever reason, read on here to find a convenient guide to the algorithms you should be using. I’m just going to cover the algorithms to use here, and not other best practices for securely using JWTs. I wrote an entire book about that (and a few other things) and there’s really just too much to try to cover in one article.
So, all that out of the way, let’s get onto the recommendations.
Encrypting data (to yourself)
A256CBC-HS512
Details: This means {“alg”:”dir”,”enc”:”A256CBC-HS512”}
and provides symmetric authenticated encryption using AES in CBC mode for encryption and HMAC with SHA-512 (truncated to 256 bits) for authentication. Use a fresh random 128-bit IV for each message, as per the spec.
You want this if you are hiding information from users or the network. In terms of JWTs, this typically crops up in things like “stateless” session cookies that will be produced and consumed by the same cluster of webservers. This is also a fine choice for access tokens or API tokens within a closed ecosystem, where you can use a shared secret securely.
Rationale: there are only two basic symmetric content encryption algorithms supported by JWTs: CBC+HMAC or AES in Galois/Counter Mode (GCM). GCM was really designed for use in interactive protocols like TLS and absolutely sucks for standalone encryption use-cases. It has a small IV space and fails catastrophically if you reuse an IV. You absolutely don’t want GCM anywhere near anything using a fixed key. Although GCM is typically faster than CBC+HMAC, for the typical sizes of JWTs (in the single-digit kB range), the difference is noise.
CBC+HMAC is a pretty boring conventional mode used by Signal and lots of other software. The JWT specs largely avoid the pitfalls that have historically impacted such algorithms (it’s Encrypt-then-MAC, the IV is included in the MAC calculation, and so on). Just make sure your JWT library implements the HMAC check in constant time and uses fresh random IVs from a secure PRNG you’re golden. The 128-bit IV space is perfectly big enough when generating random IVs, the PKCS#7 padding is actually kind of useful in partially obscuring the length of the plaintext, and HMAC is probably the most robust crypto primitive ever invented. Use the 256-bit variant for all the reasons that 256-bit keys are recommended in previous “cryptographic right answers” articles.
About the only downside to this recommendation is that it doesn’t play well with Hardware Security Modules (HSMs) due to the way the encryption and MAC sub-keys are derived from the master key (basically, by splitting it in half). But I’ve done a lot of work with HSMs (and KMSes, their cloud-native cousins) and I can tell you now that you never want to encrypt a JWT directly with a HSM. This is costly and unbearably slow (in terms of latency). Instead, you should use the HSM/KMS to encrypt a local master key (or keys) that you decrypt on-demand and then use as normal. Google calls this “envelope encryption”. Contact me if you want to discuss this kind of architecture in detail, because I’ve seen multiple companies get into trouble with bad architectures around HSMs. My rate is a lot cheaper than the mess you might otherwise make, trust me!
Don’t use any of the weird key-wrapping modes, especially not the bizarre GCM-based one.
Symmetric “signatures” (MAC)
A256CBC-HS512
Although HMAC “signatures” are probably the most widely used algorithm for JWTs, this is almost always the wrong answer in terms of JWTs, for a whole bunch of reasons:
Your JWT probably does contain some confidential data (or PII) that you probably should be encrypting. The fact that most people don’t doesn’t make it ok. Tokens can leak in all kinds of ways and unencrypted data can be readable long after the token has expired.
Symmetric encrypted JWTs are basically the same speed to process as HMAC-signed ones.
Simple signed/MACed JWTs fail open: if you forget to verify the HMAC tag on a JWT, typically nothing will fail and your application will behave the same as if it had verified it. It’s very easy to accidentally disable signature verification without realising it. On the other hand, it’s impossible to forget to decrypt a JWT because you will end up with unreadable garbage. Encrypted JWTs therefore fail closed and are more robust in the face of developer mistakes.
The way HMAC signatures are defined for JWTs you have a choice of a 256-bit security level (HS256), a 384-bit security level (HS384), or an absolutely insane 512-bit security level (HS512). I don’t really know why you’d want that. The encryption equivalents (A128CBC-HS256, A192CBC-HS384, and A256CBC-HS512) all truncate the HMAC tag to half length resulting in much more sane 128-bit, 192-bit, and 256-bit security levels and smaller MAC tags to boot. This is mostly a nitpick, but it bugs me.
So use the suggestion above and encrypt your JWTs. If you absolutely must use raw HMAC then use HS256
like everyone else.
Password handling
Don’t
If your “password” is really a high-entropy string from a password manager then by all means consider using the JWE PBKDF2 algorithm with just a single iteration. But if it’s an actual user-chosen password then you shouldn’t be using it for encryption even with 600,000+ iterations. Unlike the password storage in your database, JWTs are immediately exposed to offline brute-force/dictionary attacks and the password will be broken unless it is already high entropy.
If you instead want to encrypt a password for transport (say, if you are doing something highly dubious to a legacy system and can’t transport a salted hash), then go ahead and use the existing encryption recommendation above. CBC mode encryption pads the data to a multiple of 16 bytes, which is quite handy for obscuring the length of the password being transmitted.
Asymmetric (public key) encryption
ECDH-ES with X25519
There’s basically only two games in town for public key encryption with JWTs: some form of RSA or elliptic curves. RSA encryption is terrible and you should all stop using it. So that leaves elliptic curves in the form of ECDH-ES
: Elliptic Curve Diffie-Hellman in Ephemeral-Static mode. Phew, what a mouthful. This scheme is known outside of JWTs as ECIES (elliptic curve integrated encryption scheme), and is actually pretty simple.
You should use the X25519
curve for your keys, which is both faster and harder to screw up than other choices (in this specific use-case). However, it’s also less widely supported than other options like P-256
, which are also secure: just make sure you check your JWT library correctly handles invalid curve attacks.
If your horrible HSM or TPM only implements RSA encryption then go ahead and implement envelope encryption as described earlier. Don’t be tempted to use RSA encryption. Just don’t.
Asymmetric signatures
EdDSA or PS512
Similarly to the situation for encryption, you have a choice of RSA (in various flavours) or elliptic curves. As I’ve said before, you don’t want to use HMAC. And you definitely definitely don’t want to use “alg:none”. (Please, enough already). Also make sure you store the algorithm with the key and don’t let an attacker pick a different algorithm.
Digital signatures are all basically terrible, but of the current crop the EdDSA
scheme is the best choice. It is fast, compact, and relatively easy to implement securely. The Ed25519
curve is generally what you want.
If you can’t use EdDSA, then many cryptography engineers would recommend to use ECDSA with the P-256 curve and RFC 6979 deterministic nonces. However, in my experience RFC 6979 is not widely implemented and ECDSA bugs (like the one I found) are widespread. I would therefore recommend that if you can’t use EdDSA that you skip ECDSA and use RSA signatures. These are more bulky, and more expensive to create, but they are fast to verify: a useful property in typical sign-once, verify-many-times scenarios that JWTs are often used for. RSA-PSS in the form of PS512
is the next best choice after Ed25519. Try and stay away from RSA signatures with PKCS#1 v1.5 padding (RS512
and friends), but even those can be very robust and simple if implemented well (a big ask).
Personally I like to use the 512
variants of these algorithms, using SHA-512. This is partly because of the higher security margin in this context (which is different to the HMAC case), and partly because SHA-512 is often faster on 64-bit architectures. But also because you are then using a single consistent hash function for encryption and signatures if you follow my other recommendations.
Asymmetric authenticated encrytion
Nested signed then encrypted JWTs
The asymmetric encryption algorithms above ensure the data is confidential and cannot be tampered with after creation, but provide no data origin authentication. Anyone with access to the public key can create a valid JWT (and your public key is probably published in a JWK Set). But for many uses of JWTs you want to both protect the contents from prying eyes and also to have some assurance that it came from a trusted party. In this case, your best option at present is to first sign the JWT and then encrypt the result, using a nested signed-then-encrypted format. You can use the recommendations from the signing and encryption sections to select algorithms.
One drawback of this approach is that the resulting JWT is much larger than a normal JWT, partly due to multiple layers of base64-encoding. I have a (now expired) Internet Draft proposing an alternative approach to this problem, but it hasn’t seen much enthusiasm and my attention has moved onto a more radical approach—about which, more in a future newsletter.
Online backups
Use tarsnap
Err... backups, they sound like a good idea.