Cryptographic weaknesses often arise in applications when the core security concepts are misunderstood or misused by developers. For this reason, a thorough review of all cryptographic implementations can be a juicy target when designing an application or starting a security assessment. Often, cryptography is used in the context of communication (e.g. a key exchange or encrypted channel) or authorization (e.g. a session token). Additionally, cryptography can be used in a number of other contexts–especially when different devices or microservices are attempting to communicate over an insecure channel.
An attacker’s goal when going after session management or authorization is typically to either elevate their own privileges within an application (vertical privilege escalation) or hijack the session of another user (horizontal privilege escalation). The mantra “don’t roll your own crypto” is frequently used in the security context to warn against reimplementing or creating your own cryptographic algorithm or scheme. If a developer disregards this warning it can, and often does, lead to a security issue. Even when using tried and true concepts or libraries, issues can arise due to improper implementation.
JSON Web Tokens or JWTs are a frequently used data structure for packaging signed or encrypted data and passing it between two parties. The use of URL-safe characters makes JWTs ideal for HTTP requests/responses or other text-based channels. Additionally, nearly all major programming languages have at least one open source implementation or library to ease adoption for a wide variety of applications. The most common use case for JWTs is session tokens, where “claims” about an entity’s identity and/or role are stored in a signed token. That token can then be used in subsequent requests to make authorization decisions. A less common use case is that of encrypted JWTs, where a (typically) small amount of data is stored in an encrypted form so that only authorized parties can view its cleartext contents.
A Detour into Signing vs Encryption
Before diving into the mechanics of token formats and session management, it’s important to describe how cryptographic primitives relate to the confidentiality and integrity security principles. For many readers, this will be common knowledge, but far too often Praetorian observes that these principles are incorrectly applied in applications.
Signing, or a Digital Signature, is the process by which a piece of data is signed by one entity with a private key. This signature can then be verified by anyone with access to the corresponding public key by using an asymmetric encryption algorithm (such as RSA or ECC). If this public key is shared securely with a recipient–often by a certificate or a root of trust–then the message along with its signature has integrity. That is, if an attacker modifies the data, they cannot construct a new signature. This affords any recipient the assurance that the data has not been altered. Closely related to signatures are Message Authentication Codes (MAC), where a secret key is used to construct a MAC. For MACs, the same secret key, which created the MAC, must be known to the recipient to validate the MAC value as opposed to having public and private pieces.
This process also imbues the message with integrity, but requires sharing the same secret among multiple parties. MACs are appropriate in situations in which the sender and recipient are the same entity (ex. a session token created and used by the same domain) or have some out-of-band method for sharing secret keys (e.g. using a key exchange protocol or configuration during installation/manufacturing). Signatures are appropriate in other use cases where an entity is providing information consumed by many other (potentially untrusted) parties, or where out of band key sharing has not been established. Finally, using digital signatures in scenarios where the sender and recipient are the same entity is a perfectly fine implementation as the same entity will have both the public and private keys and can validate their own signature.
Encryption, Symmetric-Key Encryption, or a symmetric-key algorithm is the process by which an entity combines their own data (cleartext) with a secret key to create ciphertext, which can then be shared with a recipient. Unlike signatures, the original data is not shared with a recipient, but instead it is decrypted using the same secret key. Encryption imbues the data with confidentiality, but on its own does not necessarily ensure the integrity of a message. In fact, several cryptographic attacks such as the chosen-plaintext attack and the padding oracle attack are possible in part due to the fact that an encrypted message is confidential but does not have integrity. One major caveat to this integrity discussion is that modern symmetric encryption algorithms can be used in modes such as Galois/Counter mode (GCM) which are known as authenticated encryption with associated data (AEAD) modes. Roughly speaking, these modes combine an encryption algorithm with a MAC to create a message with both confidentiality and integrity.
Conversely to Symmetric-Key Encryption is Asymmetric Encryption or public key cryptography. This method uses an asymmetric algorithm (ex. RSA or ECC) and is driven by the recipient. That is, the recipient creates a public and private key pair and shares the public portion with the sender for encryption. The recipient then is the only party who can decrypt the message using their private key. This method may be used in scenarios to provide anonymity to the sender (e.g. posting to a public forum) or where the recipient does not know the sender in advance but wants to retrieve encrypted messages without first interacting with the sender (e.g. email, GPG). Like its counterpart, symmetric cryptography, this imbues a message with confidentiality, but without some associated signing algorithm, it does not provide any integrity. In fact, since the intention is that any user with access to the public key can encrypt a message, the message itself should be treated as untrusted when it is not combined with a separate signature.
The table below summarizes these concepts. In this context, “Trusted Sender” and “Trusted Recipient” mean that that entity must have access to the secret or private key.
Protocol |
Integrity |
Confidentiality |
Trusted Sender |
Trusted Recipient |
Digital Signature |
yes |
no |
yes |
no |
Message Authentication Code |
yes |
no |
yes |
yes |
Symmetric-Key Encryption |
no |
yes |
yes |
yes |
Symmetric-Key Encryption (AEAD mode) |
yes |
yes |
yes |
yes |
Asymmetric Encryption |
no |
yes |
no |
yes |
JSON Web Token Formats
JSON Web Tokens (JWT) are commonly used in many different applications which require some sort of cryptographic primitive. Most often, the JSON Web Signature (JWS) structure is chosen as its contents are signed and not encrypted; however, the JSON Web Encryption (JWE) structure may also be used to make a JWT. Generally speaking, JWS and JWE are used to define the cryptographic format for a “Compact Serialization” or “JSON Serialization” structure. However, the RFCs for both of these structures specify that the payload may be of any format (strings, JSON, raw bytes, etc.). Conversely, the RFC for JWTs defines a payload format (JSON) as well as claims that should go in both the Javascript Object Signing and Encryption (JOSE) header and payload. In common vernacular, JWT is used to mean “JWT using the JWS Compact Serialization” format. Given that this blog post is primarily about cryptography, the claim values in JWTs are not as relevant as the format of JWS and JWE structures themselves.
JSON Web Signature (JWS)
The JWS structure is defined in RFC 7515. The JWS has three elements – the JOSE header, the payload, and the signature. When used to construct a “compact serialization” object, the three elements are URL-base64 encoded and concatenated with a period (‘.’) between them. Several examples of the JWS format can be found in the appendix of the RFC.
For encryption purposes, the JOSE header must specify the cryptographic algorithm used in the “alg” header. Algorithms used can be either hashing algorithms–such as “HS256” for HMAC SHA-256–or asymmetric encryption algorithms–such as “RS256” for the RSASSA-PKCS1-v1_5 SHA-256 digital signature. Additionally, implementations may choose to include a “kid” in the JOSE header to specify which key ID was used to sign the JWT.
Returning to the previous section, the use of a JWS with either a digital signature or MAC algorithm will create a token which has integrity–it cannot be modified without reconstructing the signature. For scenarios where the user of the JWS does not need to validate the signature, but merely present it to another user, a MAC algorithm may be used. However, if an entity is provided with a JWS that uses asymmetric encryption and the corresponding public key is available, then the validity of the token may be validated by using a shared public key.
In summary, the two formats have the following cryptographic properties:
Protocol |
“alg” Example |
Integrity |
Confidentiality |
Created by |
Validated by |
JWS symmetric |
HS256 |
yes |
no |
trusted secret key holder |
trusted secret key holder |
JWS asymmetric |
RS256 |
yes |
no |
trusted private key holder |
untrusted public key holder |
Common use cases for each of these token types might be:
- JWS symmetric – a session token for an application consisting of a single service, a session token used for several services which are all trusted, an oauth refresh token issued and consumed by the same service or endpoint.
- JWS asymmetric – an identity token which asserts “claims” about a user and can be validated by any service with the corresponding public key, a session token consumed by a variety of microservices not all of which are “trusted” by the issuer.
JSON Web Encryption (JWE)
The JWE structure is defined in RFC 7516 and is slightly more complex than the JWS. This structure has five elements – the JOSE header, the encrypted content encryption key (cek), the initialization vector (IV), the ciphertext, and the authentication tag. Like the JWS, the “compact serialization” object is constructed by URL-base64 encoding and concatenating each of these five sections with a period (‘.’). Examples of the JWE format can be found in the appendix of the RFC.
For encryption purposes, the RFC specifies that two cryptographic algorithms must be chosen – the “alg” used to encrypt the content encryption key (cek) and the “enc” which uses the cek to encrypt the payload and construct an authentication tag. The “alg” parameter may be an asymmetric encryption–such as “RSA-OAEP” for RSAES-OAEP algorithm–or a symmetric encryption algorithm–such as “A128KW” for the AES 128 CBC with HMAC SHA-256. The “enc” parameter must be an authenticated encryption (AEAD) algorithm such as “A256GCM” for AES GCM with a 256-bit key.
Looking at how cryptographic algorithms are used in JWEs is slightly confusing. Clearly since an AEAD algorithm is required for “enc”, this implies that all JWEs have both integrity and confidentiality. This is true in that the IV and ciphertext elements cannot be modified without altering the authentication tag; however, this seems to ignore the two preceding elements in a JWE. When considering that the “alg” algorithm can either be asymmetric or symmetric encryption, this means that the cek itself may have integrity or not. Furthermore, if asymmetric encryption is used for the “alg” parameter and the public key is available, then any user may use that public key to encrypt a new cek with the “alg” and then encrypt their own payload using the new cek, thereby constructing their own JWE from scratch.
Revisiting the security concepts again, this gives the following:
Protocol |
“alg” (example) |
Integrity |
Confidentiality |
Encrypted by |
Decrypted by |
JWE symmetric |
A128KW |
yes |
yes |
trusted secret key holder |
trusted secret key holder |
JWE asymmetric |
RSA-OAEP |
no |
yes |
untrusted public key holder |
trusted private key holder |
Again, some common use cases for each of these token types might be:
- JWE symmetric – any of the JWS symmetric use cases where some claim data needs to be hidden from the end user, a data structure used to pass data securely between two trusted services via an insecure medium.
- JWE asymmetric – data exchanges where an untrusted user is required to send confidential data to a trusted service.
JWT Playground
To provide some hands-on experimentation with JWS and JWE tokens, Praetorian created a challenge which uses simplified tokens with a limited set of endpoints. Rather than host this service, we’ve pushed an image to dockerhub for local testing. Interested parties should attempt to use the concepts described in this post to see how different JWT formats could be used or abused in this environment. To try the image yourself simply run the following commands after installing Docker on your local machine.
|
Conclusion
JWTs are a convenient wrapper for signed or encrypted data, and they are often used as cryptographic primitives in session management and authorization. When used for their correct and intended purposes, they are difficult, if not impossible, for an attacker to leverage as a security vulnerability. However, some inexperienced developers may have an impulse to “encrypt everything” and assume that because something is encrypted it cannot be modified. As the above discussion describes, this may not always be the correct choice–especially if data integrity is required instead of confidentiality.