In Part 1, we looked at OAuth 2.0, and OIDC token signatures, trust, and integrity. This second part is about implementing data confidentiality. This is where we look at token encryption. Encrypting tokens is not always exercised, and may well be something that is overlooked because of the added complexity. Welcome to Part 2 of applied cryptography for OIDC and OAuth 2.0 tokens.
This article starts by describing the list of options available for cryptography. The following section discusses key sharing algorithms’ performance, and some aspects of security. Finally, this concludes with the actual token encryption options. There is also a TL; DR section at the bottom (yeah!).
As with the previous article, I proposed to start with the raw list of formal options which we need to pick from. The first obvious difference with token encryption compared to signatures is that here we have two tables. Table 1 is a list of key sharing strategies, and Table 2 is the list of encryption algorithms.
Table 1: JWE supported algorithms for the encryption of the key
Table 2: JWE supported algorithm for the encryption of the content
Again, there is a lot of information packed in those tables, so let’s see what we have. Table 1 lists four types of algorithms RSA, AES, ECDH, and PBES:
- RSA: Rivest Shamir Alderman a.k.a. public/private encryption keys, which we have already seen in Part 1.
- AES: Advanced Encryption Standard is a widespread symmetric encryption algorithm.
- ECDH: Elliptic Curve Diffie Hellman is another public/private key encryption solution.
- PBES: Password Based Encryption Scheme is, as the name suggests, a means of generating keys from passwords.
Key exchange is the guarantee of confidentiality. In applied cryptography, it is the hard part. We will look at how JWE proposes to solve this. Table 2, which references the encryption algorithms for the tokens, simply shows two versions of the AES encryption algorithm which we will compare.
So, the crucial step here is how to pick a key sharing solution. While it has many (seventeen) algorithms to choose from, the JSON Web Algorithms list (JWA) key sharing standards can be divided into four broad categories: key wrapping, key encryption, direct key agreement, and key agreement with key wrapping. What we are trying to share is the content encryption key (CEK) which is the one we use to encrypt or decrypt the data. We also want to protect the CEK from unwarranted parties. Each key sharing strategy presents interesting characteristics which will help narrow down our algorithm selection. Let us go through them one by one:
- Key wrapping: This involves another key called the key encryption Key (KEK). The KEK is used to encrypt the CEK. When the CEK is in its encrypted form, it is called a JWE encrypted key. So, with key wrapping, you still have the KEK to share between the communicating parties (i.e., the authorization server and the relying party or client). There is still a benefit to proceeding in this way. The authorization server does not need to keep storing every key it ever creates to encrypt tokens. Another benefit is content deletion;, if the KEK is discarded, the token is no longer accessible. This is why key wrapping is popular for content that is distributed in untrusted environments such as public clouds. The main drawback of this approach is that the KEK needs to be exchanged beforehand somehow. It is not very practical when there are many client/relying parties consuming tokens. Obviously they all need different keys and every so often they need to be refreshed.
- Key encryption: This time, the CEK is encrypted using an asymmetric algorithm (RSA). The recipient needs to share a public key either in text format, or in JSON web key (JWK) format, or as a JWK URI³. The JWK format is described in RFC 7517⁴. This makes things simpler than key wrapping, especially when the recipient and the authorization server are two distinct entities; typically, the recipient will share a public URI. For key rotation, the recipient has to share a new key or update the URI. The obvious benefit is that there is no key to share secretly. However, the standard is starting to date and we will see that there is another issue which renders this solution less attractive—performance.
Direct key agreement: A key agreement algorithm is used to generate the CEK (there is no KEK). The key agreement principle may be less commonly understood than RSA, but it brings really good benefits. JWE recommends a real mouthful: the Elliptic Curve Diffie Hellman-Ephemeral Static with concatenation key derivation function. The intention is not to explain the cryptography behind this. Luckily, there is ample academic literature available for that. Yet, we want to know when to use this so we need to explain briefly what the different parts are:
The Diffie Hellman part is the established algorithm to generate and share a symmetric key securely. It means that two parties enter a dialogue and they both end up with the same shared secret. This protocol is baked in long established standards such as Transport Layer Security (TLS). This means it is everywhere and we use it online all the time.
The Elliptic Curve part is a variant of the algorithm using said curves. The algorithm involves public keys that can be static (always the same ones) or ephemeral (new keys are generated for each cipher text). Here, the Ephemeral Static part means that the sender uses ephemeral keys, and the recipient sends static keys.
The concatenation key derivation function (concat KDF) is used to generate the final key to its desired size.
In theory, the level of security that can be achieved with Elliptic Curves is higher than with the RSA algorithm. The down-side may be that, as it involves Elliptic Curve cryptography, it is easy to get things wrong and implement poor security.
- Key agreement with key wrapping: This is a hybrid solution. The same key agreement algorithm described above is used to generate the KEK. The CEK is therefore encrypted with the generated KEK. This means we can implement key wrapping and we do not have to share the KEK separately. This does add complexity, but it brings a subtle benefit because the JWE encryption key can be kept with the token, and the KEK can be managed separately. This approach may be more familiar to a recipient used to handling KEK.
- Direct encryption: For the purpose of this article, it is not counted as a key sharing strategy, because in direct encryption, the CEK itself is exchanged, so there is no real key sharing implementation.
Table 3 shows how algorithms from Table 1 are classified. Hopefully, even if you are not a cryptographer, Table 1 and 2 should start to make more sense. We can carry on with performance analysis.
Table 3: JWE algorithm classification
Cryptography adds an overhead to CPU computation, and it is good to be able to gage the impact. I started this overview with the performance side of the JWE algorithms. We have two figures; Figure 1 is for the authentication servers in charge of generating the CEK and implementing the key sharing solutions. Figure 2 is for the client/relying party in charge of recovering the CEK. The test consists of running the process 1,000 times and checking how long it takes in milliseconds. Note that a few of the JWE algorithm variations are intentionally omitted so as to avoid overcrowding the figures. The tests were run on my laptop which has the following CPU:
2,4 GHz Intel Core i9 8 cores — T2 Hardware Encryption Acceleration chip (no specification details known)
We observe that RSA encryption is acceptable but the decryption is literally off the chart (indeed, it could not be plotted on this chart, or it would have dwarfed everything else). The time it took to decrypt was over 10,000 milliseconds for the 1,000 iterations, so, well off the chart, meaning RSA decryption went off like a lead balloon, and we need something better.
Expectedly, the AES key wrap solution is the simplest and therefore the fastest to generate an encrypted key, or to recover it. The alternative to RSA public/private key solution is ECDH . We can see that performance varies significantly depending on the curve chosen. Curves P-384, and P-521 show poor results. The P-256 curve is the only one with good performance. Note that the ephemeral part of the algorithm means that performance is impacted because it needs to include a key generation which RSA does not have to do. However, I also threw in the popular X25519, and X448 (not the JWE standard, strictly speaking, but good software vendors are already implementing them anyway), and the former is so good it almost compares with AES encryption, while the latter is not as fast, but still very performant. With the hybrid key agreement and key wrapping, the results are similar, albeit slightly slower, due to the added key wrapping part, but performance is largely driven by the ECDH algorithm. Finally, the AES GCM with key wrapping and the PBES2 algorithm show very acceptable performances both with encryption and decryption.
Figure 1: Key Sharing algorithm performance authentication server side (CPU-related); i.e., time-generated an encrypted content encryption key in milliseconds (1000 iterations).
Figure 2: Key Sharing algorithm performance recipient side (CPU-related); i.e., time to recover a content encryption key in milliseconds (1000 iterations).
Now we have had an overview we can look at those solutions in more details. There are two aspects that we are mostly interested in: theoretical strength and performance.
The JWE standard includes three flavors of the RSA encryption scheme RSAES-PKCS1-v1_5, RSA OAEP, and RSA OAEP using SHA-256 and MGF1 with SHA-256. The first one is being deprecated, it is the original encryption scheme with public key cryptography standard; it is not evaluated here. It has been replaced with the RSA optimal asymmetric encryption padding (RSA OAEP). This is considered more secure against a number of attacks. The third option adds a mask generating function (MGF1), allowing the cipher text to be stretched with padding of a desired length. This makes it a slightly more secure choice (apparently). As with any other RSA-based solution, the key size is important for security. A minimum size of 2048 bits is expected Part 1 discusses RSA key sizes in more detail; the same applies here). The performance tests show that encryption is reasonably fast, but decryption is orders of magnitude slower. As a result, it may not be the best choice to a client/relying party that is already running high on CPU power. Due to poor performance, RSA-based solutions seem to be better suited to signatures than encryption.
This the simplest solution of all. It relies on the standard AES key wrapping; i.e., encrypting the decryption key with the data. The algorithm does not require extra input, but the KEK needs to be shared in some way beforehand. This is a simple choice for simple small scale deployments. The size of the KEK can be 128-bit, 192-bit, or 256-bit. The size of the KEK does not noticeably affect performance. One advantage of this key wrapping solution is that there is no need to pass any parameters to the recipient. It does, however, introduce some risk of data tampering; therefore, the environment and the participants should all be trusted.
This is the other public/private key solution that is available to encrypt the CEK. The algorithm yields a CEK directly. The implementation uses Ephemeral-Static keys, meaning that the authentication server has ephemeral keys, and the recipient uses a static key. This means that the recipient can be authenticated and helps prevent a man in the middle attack⁶. The sender needs to share the Ephemeral Public Key (EPK); other parameters are optional. The recipient shares a static public key using either a JWK, or a URI.
With ECDH, the key size is directly related to the selected curve⁷. P-256 provides a 128-bit security which matches RSA 3072 bit, P-384 is equivalent to an RSA 7680 bit, and P-521 is akin to an RSA 15360 bit. The X25519 curve provides a 128-bit security therefore also equivalent to P-256 or RSA 3072, but considerably faster. Finally, X448 has 224-bit of security. This is at the same level as a 10498 bit RSA key, and with a performance roughly comparable to a P-256 curve!
We have seen that the curve significantly affects performance with Figures 1 and 2. Standard curves outperform RSA key encryption for an equivalent security, but non- standard curves (X25519, and X448) outperform standard ones. Therefore, ECDH is a faster and stronger solution than RSA. Elliptic Curves cryptograhy does add complexity that should be reviewed carefully, so as to not introduce security loopholes.
The algorithm reveals a KEK that can be used to decrypt the CEK. This approach might be interesting if tokens are distributed to different services and decryption is managed by means of releasing the KEK to authorized systems. As for the direct agreement, the sender shares at least an Ephemeral Public Key (EPK). The recipient shares a Static Public Key with a JWK, or a URI.
The ECDH part is the same as direct key agreement described above, and therefore, the performance is also the same if we ignore the small impact of the AES Wrap. The Key Wrap does not really add to the theoretical security. Choosing this option is related to the token decryption management. This can be a good choice if the OIDC or OAuth 2.0 tokens have multiple destinations and end-to-end encryption is a requirement. Again, security should be checked carefully when implementing Elliptic Curves.
This is a type of AES Key Wrap that has an additional property called Authenticated Encryption provided by the Galois/Counter Mode (GCM). This means that it provides protection against "chosen cipher text attack"⁸. The attack consists of analyzing decryption of some selected cipher text in order to retrieve the decryption key. The GCM part introduces two mandatory parameters: an Initialisation Vector (IV), and a Tag (a.k.a. Authentication Tag). The sender generates the IV which is a 96-bit random string. The tag is generated by the algorithm and is a 128-bit string. The recipient needs to have received the KEK beforehand. Therefore, the decryption requires the cipher text, the KEK, the IV, and the Tag. If we operate in a non-trusted environment, this solution is superior to the AES Key Wrap from a security standpoint.
The standard has three key sizes: 128-bit, 192-bit, and 256-bit. This is the same as the AES Key Wrap alone. We can observe a minimal impact on performance due to these overheads compared to the AES Key Wrap.
The PBES2 means Password Based Encryption Scheme no 2. It uses a password based Key Derivation Function. This combines a password with hashing and salting to provide a key of the desired length. The sender and the recipient have shared a password (which is likely to be the client password). The sender produces the KEK and uses it to wrap the CEK. The sender shares two parameters for the recipient to recompose the KEK: PBES2 Salt Input (P2S), and an iteration counter for the hashing called PBES2 Count (P2C).
Again keys can be of three sizes: 128-bit, 192-bit, and 256-bit. As we can see from Figures 1 and 2, Key sizes’ impact on performance is barely noticeable. The benefit of this Key Wrap is that the password helps preventing the exchange of the KEK. However, this implies that a password management is put in place with the usual password reset, password policy, etc. This also means that the password may not be hashed because it is required to produce the KEK.
Now that we have reviewed key exchange, we can look at the encryption of the token itself. This is described in Table 2 (reproduced here for convenience):
Table 2 (same as the one all the way up): JWE supported algorithm for the encryption of the content
We can see that there is one type of encryption: Advanced Encryption Standard (AES), and that there are two variations available (one of which that we have already seen above). Both types of encryption are categorized as Authenticated Encryption algorithms, and both use a combination of Initialisation Vector and Hashing to create the Authenticated Encryption property. In practice, both algorithms produce an authentication tag which is checked by the recipient for tampering.
The AES CBC is an acronym for Cipher Block Chaining, and the AES GCM is an acronym for Galois/Counter Mode. The former is a block cipher (it handles chunks of data), and the latter a stream cipher (it handles one byte at a time). Other than that options are pretty much the same.
In terms of security, 128-bit is considered fully resistant to brute force, so it may be used safely. However, only a 256-bit AES key is considered quantum resistant⁵, which may be totally anecdotal in the context of OIDC or OAuth 2.0 tokens that tend to be short-lived anyway.
Figure 3: JWE content encryption and decryption per algorithm for 10,000 iterations in milliseconds
Figure 3 shows the performance comparison between all the algorithms. Basically, the result is that AES GCM is more performant than the AES CBC version.
Now we have looked at token signatures and encryption from an applied cryptograhy perspective. Note that if the token is both signed and encrypted, the correct order is signed first and encrypted after as mentioned in the OpenID standard definition⁹.
Choosing encryption for OIDC tokens requires much pondering and back and forth conversations with— rightfully —security minded colleagues. This can be a source of delay and frustration. In the worst case, it can lead to poorly implemented security. Hopefully, this article has shed some light on how to select the algorithm that best suits a given use case.
I see a lot of articles including a TL; DR section so I also got mine here:
You have five standard ways of setting up OIDC token encryption:
- Key wrapping: Choose the tamper-proof solution (AES GCM) if you can. Or maybe the password-based encryption (PBES2) if you have to.
- Key encryption: RSA public/private scheme. Don’t do that one because it is too slow. Do the next one.
- Key agreement: ECDH public/private scheme. Fast and scalable solution. More complex than RSA but also more secure, and performance can be really good if you use the non standard curves.
- Key agreement with key wrap: ECDH also same as the above. Better suited when OIDC tokens are distributed to different systems, as you may split the decryption key and share it with a centrally managed key distribution (or something similar).
- Alternatively, forget about standards—just encrypt and share the key.
Thanks to Ali S, and Christian B who helped me with both part 1 and 2 giving me invaluable advice.
- RFC 7516 - JSON Web Encryption (JWE)
- RFC 7518 - JSON Web Algorithms (JWA)
- URI: Universal Resource Identifier
- RFC 7517 - JSON Web Key (JWK)
- Grover's algorithm - Wikipedia
- Elliptic-curve Diffie–Hellman - Wikipedia
- Authenticated encryption - Wikipedia
- Final: OpenID Connect Core 1.0 incorporating errata set 1