Using JSON Web Encryption (JWE)
JSON Web Encryption (JWE) could be used when one needs to add sensitive information to a token that one would not want to share with other systems.
Join the DZone community and get the full member experience.
Join For FreeIn the previous article, we looked at signed JSON Web Tokens and how to use them for cross-service authorization. But sometimes, there are situations when you need to add sensitive information to a token that you would not want to share with other systems. Or such a token can be given to the user's device (browser, phone). In this case, the user can decode the token and get all the information from the payload.
One solution to such a problem could be the use of JSON Web Encryption (JWE), the full specification of which can be found in RFC7516.
JSON Web Encryption (JWE)
JWE is an encrypted version of JWT and looks like this:
It consists of the following parts separated by a dot:
BASE64URL(UTF8(JWE Protected Header)) || '.' ||
BASE64URL(JWE Encrypted Key) || '.' ||
BASE64URL(JWE Initialization Vector) || '.' ||
BASE64URL(JWE Ciphertext) || '.' ||
BASE64URL(JWE Authentication Tag)
JWE Protected Header
For example:
{
"enc": "A256GCM",
"alg": "RSA-OAEP-256"
}
where
alg
– The Content Encryption Key is encrypted to the recipient using the RSAES-OAEP algorithm to produce the JWE Encrypted Key.enc
– Authenticated encryption is performed on the plaintext using the AES GCM algorithm with a 256-bit key to produce the ciphertext and the Authentication Tag.
JWE Encrypted Key
Encrypted Content Encryption Key value.
JWE Initialization Vector
Randomly generated value needed for the encryption process.
JWE Ciphertext
Encrypted payload.
JWE Authentication Tag
Computed during the encryption process and used to verify integrity.
Token Generation
There are many libraries for many programming languages to work with JWE tokens. Let's consider as an example the Nimbus library.
build.gradle:
implementation 'com.nimbusds:nimbus-jose-jwt:9.25.6'
The payload can be represented as a set of claims:
{
"sub": "alice",
"iss": "https://idp.example.org",
"exp": 1669541629,
"iat": 1669541029
}
Let's generate a header:
JWEHeader header = new JWEHeader(
JWEAlgorithm.RSA_OAEP_256,
EncryptionMethod.A256GCM
);
which corresponds to the following JSON:
{
"enc": "A256GCM",
"alg": "RSA-OAEP-256"
}
Let's generate an RSA key:
RSAKey rsaJwk = new RSAKeyGenerator(2048)
.generate();
Using the public part of the key, we can create an Encrypter object, with which we encrypt the JWT:
RSAEncrypter encrypter = new RSAEncrypter(rsaJwk.toRSAPublicKey());
EncryptedJWT jwt = new EncryptedJWT(header, jwtClaims);
String jweString = jwt.encrypt(encrypter);
Execution result:
eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.O01BFr_XxGzKEUb_Z9vQOW3DX2cQFxojrRy2JyM5_nqKnrpAa0rvcPI_ViT2PdPRogBwjHGRDM2uNLd1BberKQlaZYuqPGXnpzDQjosF0tQlgdtY3uEZUMT-9WPP8jCxxQg0AGIm4abkp1cgzAWBQzm1QYL8fwaz16MS48ExRz41dLhA0aEWE4e7TYzjrfaK8M4wIUlQCFIl-wS1N3U8W2XeUc9MLYGmHft_Rd9KJs1c-9KKdUQf6tEzJ92TGEC7TRZX4hGdtszIq3GGGBQaW8P9jPozqaDdrikF18D0btRHNf3_57sR_CPEGYX0O4mY775CLWqB4Y1adNn-fZ0xoA.ln7IYZDF9TdBIK6i.ZhQ3Q5TY827KFQw8DdRRzQVJVFdIE03B6AxMNZ1sQIjlUB4QUxg-UYqjPJESPUmFsODeshGWLa5t4tUri5j6uC4mFDbkbemPmNKIQiY5m8yc.5KKhrggMRm7ydVRQKJaT0g
To decode a JWE token, you need to create a Decryptor object and pass to it the private part of the key:
EncryptedJWT jwt = EncryptedJWT.parse(jweString);
RSADecrypter decrypter = new RSADecrypter(rsaJwk.toPrivateKey());
jwt.decrypt(decrypter);
Payload payload = jwt.getPayload();
Here, we used an asymmetric encryption algorithm — the public part of the key is used for encryption, and the private part for decryption. This approach allows issuing JWE tokens for third-party services and being sure that the data will be protected (when there are intermediaries in the token transmission path). In this case, the final service needs to publish the public keys, which we will use to encrypt the content of the token. To decrypt the token the service will use the private part of the key, which it will keep secret.
But what if we have the same Issuer and Consumer token service? It could be a backend that sets a cookie in the user's browser with sensitive information. In that case, you don't need to use an asymmetric algorithm — you can use a symmetric algorithm. In JWE terms, this is direct encryption.
JWEHeader header = new JWEHeader(JWEAlgorithm.DIR, EncryptionMethod.A128CBC_HS256);
which corresponds to the following JSON:
{
"enc": "A128CBC-HS256",
"alg": "dir"
}
Let's generate a 256-bit key:
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey key = keyGen.generateKey();
and encrypt JWT:
JWEObject jweObject = new JWEObject(header, jwtClaims.toPayload());
jweObject.encrypt(new DirectEncrypter(key));
String jweString = jweObject.serialize()
Execution Result:
eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..lyJ_pcHfp8cz13TVav8MZQ.LmeN4jHxYg-dEFZ98PlVfNXFI29L5NGanA6ncALWcI9uDqpoXaaBcKeOKuzRayfQ3X7yPTuiMRHAUHMR5K3Rucmb8fQw2dkP3EONUg0lbdbmfbNwDbjQcWCGUWXfBWFg.v63pTlB7B15ZLEwSBwBUAg
Note that direct encryption does not include the JWE Encrypted Key part of the token.
To decrypt the token, you need to create a Decryptor from the same key:
EncryptedJWT jwt = EncryptedJWT.parse(jweString);
jwt.decrypt(new DirectDecrypter(key));
Payload payload = jwt.getPayload();
Performance
To evaluate the performance of symmetric and asymmetric encryption algorithms, a benchmark was conducted using the JHM library. RSA_OAEP_256/A256GCM was chosen as the asymmetric algorithm, A128CBC_HS256 as the symmetric algorithm. The tests were run on a Macbook Air M1.
The payload:
{
"iss": "https://idp.example.org",
"sub": "alice",
"exp": 1669546229,
"iat": 1669545629
}
Benchmark results:
Benchmark Mode Cnt Score Error Units
Asymmetric Decrypt thrpt 4 1062,387 ± 4,990 ops/s
Asymmetric Encrypt thrpt 4 17551,393 ± 388,733 ops/s
Symmetric Decrypt thrpt 4 152900,578 ± 1251,034 ops/s
Symmetric Encrypt thrpt 4 122104,824 ± 5102,629 ops/s
Asymmetric Decrypt avgt 4 0,001 ± 0,001 s/op
Asymmetric Encrypt avgt 4 ≈ 10⁻⁴ s/op
Symmetric Decrypt avgt 4 ≈ 10⁻⁵ s/op
Symmetric Encrypt avgt 4 ≈ 10⁻⁵ s/op
As expected, asymmetric algorithms are slower. According to the test results, more than ten times slower. Thus, if possible, symmetric algorithms should be preferred to increase performance.
Conclusion
JWE tokens are quite a powerful tool and allow you to solve problems related to secure data transfer while taking all the benefits of self-contained tokens. At the same time, it is necessary to pay attention to performance issues and choose the most appropriate algorithm and key length.
Opinions expressed by DZone contributors are their own.
Comments