Skip to content
Connect2id

JSON Web Token (JWT) with RSA encryption

RSA is an algorithm for asymmetric (public key) encryption, invented more than 40 years ago and in wide use today.

Encrypting a JWT for a given recipient requires their public RSA key:

JWEEncrypter encrypter = new RSAEncrypter(rsaPublicKey);

The decryption takes place with the corresponding private RSA key, which the recipient must keep secret at all times:

JWEDecrypter decrypter = new RSADecrypter(rsaPrivateKey);

A critical security requirement in public key encryption is ensuring that data is encrypted for the intended recipient – failure to do so compromises confidentiality. One common solution is a public key infrastructure (PKI), such as that based on the PKIX / X.509 standard, which underpins SSL/TLS on the Internet and other systems.

Public key encryption is effectively a two-step process, due to an RSA limitation that makes it impractical to encrypt data larger than a few hundred bytes. This complexity is handled internally by the Nimbus JOSE+JWT library – you only need to provide the recipient’s public key, the RSA algorithm (alg), and the content encryption algorithm (enc) to encrypt data, such as the claims in a JWT.

If you’re curious about the two encryption steps involved:

  1. A randomly generated, single-use symmetric key – called the Content Encryption Key (CEK) – is created to encrypt the JWT payload using a cipher like AES or ChaCha20. These symmetric algorithms are highly efficient and can handle plaintexts of virtually any size. The specific type and length of the CEK are determined by the JWE enc header. For example, A128GCM indicates that a 128-bit AES key should be generated.

  2. The generated CEK, which is small enough to fit within RSA’s encryption limits, is then encrypted using the recipient’s public RSA key, as specified by the JWE alg header. This encrypted CEK is included as part of the final JWT.

There are two standard RSA algorithm types for JSON Web Encryption (JWE), identified by the JWE alg header parameter:

JWE alg Description
The CEK is encrypted with RSAES with Optimal Asymmetric Encryption Padding (OAEP). Use RSA-OAEP-256 or another SHA-2 based RSA algorithm. Don't use RSA-OAEP because it's SHA-1 hashing is considered weak for today's applications.
The CEK is encrypted with RSAES-PKCS1-v1_5. Use of this algorithm is generally not recommended due to a security vulnerability. Provided for backward compatibility with older software.

A JWE alg can be combined with any of the following content encryption algorithms, identified by the JWE enc header parameter:

JWE alg Description
AES/CBC/HMAC/SHA-2 authenticated encryption
AES/GCM
eXtended-nonce ChaCha / Poly1305

The following example demonstrates JWT encryption with RSA-OAEP-256 and A128GCM. The RSAPublicKey of the recipient is used to perform the encryption. The decryption takes place with the recipient’s RSAPrivateKey.

Check out EncryptedJWTTest.java for the complete code.

import java.util.*;
import java.security.interfaces.*;
import javax.crypto.*;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;

// Compose the JWT claims set
Date now = new Date();

JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder()
    .issuer("https://openid.net")
    .subject("alice")
    .audience(Arrays.asList("https://app-one.com", "https://app-two.com"))
    .expirationTime(new Date(now.getTime() + 1000*60*10)) // expires in 10 minutes
    .notBeforeTime(now)
    .issueTime(now)
    .jwtID(UUID.randomUUID().toString())
    .build();

System.out.println(jwtClaims.toJSONObject());
// Produces
// {
//   "iss" : "https://openid.net",
//   "sub" : "alice",
//   "aud" : [ "https://app-one.com" , "https://app-two.com" ],
//   "exp" : 1364293137871,
//   "nbf" : 1364292537871,
//   "iat" : 1364292537871,
//   "jti" : "165a7bab-de06-4695-a2dd-9d8d6b40e443"
// }

// Request JWT encrypted with RSA-OAEP-256 and 128-bit AES/GCM
JWEHeader header = new JWEHeader(
    JWEAlgorithm.RSA_OAEP_256,
    EncryptionMethod.A128GCM
);

// Create the encrypted JWT object
EncryptedJWT jwt = new EncryptedJWT(header, jwtClaims);

// Create an encrypter with the specified public RSA key
RSAEncrypter encrypter = new RSAEncrypter(publicKey);

// Do the actual encryption
jwt.encrypt(encrypter);

// Serialise to JWT compact form
String jwtString = jwt.serialize();

System.out.println(jwtString);
// Produces
//
// eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.K52jFwAQJH-
// DxMhtaq7sg5tMuot_mT5dm1DR_01wj6ZUQQhJFO02vPI44W5nDjC5C_v4p
// W1UiJa3cwb5y2Rd9kSvb0ZxAqGX9c4Z4zouRU57729ML3V05UArUhck9Zv
// ssfkDW1VclingL8LfagRUs2z95UkwhiZyaKpmrgqpKX8azQFGNLBvEjXnx
// -xoDFZIYwHOno290HOpig3aUsDxhsioweiXbeLXxLeRsivaLwUWRUZfHRC
// _HGAo8KSF4gQZmeJtRgai5mz6qgbVkg7jPQyZFtM5_ul0UKHE2y0AtWm8I
// zDE_rbAV14OCRZJ6n38X5urVFFE5sdphdGsNlA.gjI_RIFWZXJwaO9R.oa
// E5a-z0N1MW9FBkhKeKeFa5e7hxVXOuANZsNmBYYT8G_xlXkMD0nz4fIaGt
// uWd3t9Xp-kufvvfD-xOnAs2SBX_Y1kYGPto4mibBjIrXQEjDsKyKwndxzr
// utN9csmFwqWhx1sLHMpJkgsnfLTi9yWBPKH5Krx23IhoDGoSfqOquuhxn0
// y0WkuqH1R3z-fluUs6sxx9qx6NFVS1NRQ-LVn9sWT5yx8m9AQ_ng8MBWz2
// BfBTV0tjliV74ogNDikNXTAkD9rsWFV0IX4IpA.sOLijuVySaKI-FYUaBy
// wpg

// Parse
jwt = EncryptedJWT.parse(jwtString);

// Create a decrypter with the specified private RSA key
RSADecrypter decrypter = new RSADecrypter(privateKey);

// Decrypt
jwt.decrypt(decrypter);

// Retrieve JWT claims
System.out.println(jwt.getJWTClaimsSet().getIssuer());
System.out.println(jwt.getJWTClaimsSet().getSubject());
System.out.println(jwt.getJWTClaimsSet().getAudience());
System.out.println(jwt.getJWTClaimsSet().getExpirationTime());
System.out.println(jwt.getJWTClaimsSet().getNotBeforeTime());
System.out.println(jwt.getJWTClaimsSet().getIssueTime());
System.out.println(jwt.getJWTClaimsSet().getJWTID());