JSON Web Token (JWT) with RSA encryption

RSA is a commonly used algorithm for asymmetric (public key) encryption. To encrypt a JWT for a given recipient you need to know their public RSA key. Decryption happens with the private RSA key, which the recipient must keep secure at all times.

To create an RSA encrypter for a given public key:

JWEEncrypter encrypter = new RSAEncrypter(rsaPublicKey);

To create an RSA decrypter:

JWEDecrypter decrypter = new RSADecrypter(rsaPrivateKey);

An essential security aspect in public key encryption is ensuring data is encrypted for the intended recipient, and not some for other party, which may compromise the data's confidentiality. This requires a public key infrastructure to be set up, such as PKIX / X.509 (used for SSL/TLS on the internet).

The actual public key encryption is a two step process, to work around an RSA limitation on the amount of bytes that can be encrypted to just a few hundred. The library takes care of this internally, so you only need to input the expected public key, RSA algorithm (alg) and content encryption algorithm (enc).

If you still want to know what these two encryption steps are:

  1. A single use secret AES key (called Content Encryption Key, or CEK) is generated to encrypt the JWT payload. The AES ciphers are super efficient and accept data of (almost) arbitrary size. The AES key length (128, 192 or 256 bit) and mode is set by the "enc" JWE header parameter.
  2. The AES key is then encrypted using RSA according to the set "alg" JWE header parameter, and sent as part of the JWT.

The JOSE standard makes two RSA-based algorithms for JSON Web Encryption (JWE) available, identified by the "alg" header parameter:

  • RSA-OAEP and RSA-OAEP-256 -- where the CEK is encrypted with RSAES with Optimal Asymmetric Encryption Padding (OAEP).
  • RSA1_5 -- where the CEK is encrypted with RSAES-PKCS1-v1_5. Use of this algorithm is not recommended due to a security vulnerability; it's only provided for backward compatibility with older software.

You can pair them with any of the following AES algorithms for content encryption, identified by the "enc" header parameter:

  • A128CBC-HS256 - AES_128_CBC_HMAC_SHA_256 authenticated encryption
  • A192CBC-HS384 - AES_192_CBC_HMAC_SHA_384 authenticated encryption
  • A256CBC-HS512 - AES_256_CBC_HMAC_SHA_512 authenticated encryption
  • A128GCM - AES GCM using 128-bit key
  • A192GCM - AES GCM using 192-bit key
  • A256GCM - AES GCM using 256-bit key

The following example demonstrates RSA-OAEP-256 with A128GCM encryption of a JWT, where the recipient's java.security.interfaces.RSAPublicKey is used to encrypt the JWT. The recipient can then decrypt the JWT with its java.security.interfaces.RSAPrivateKey.

Check out /src/test/java/com/nimbusds/jwt/EncryptedJWTTest.java to see 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 back
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().size());
System.out.println(jwt.getJWTClaimsSet().getExpirationTime());
System.out.println(jwt.getJWTClaimsSet().getNotBeforeTime());
System.out.println(jwt.getJWTClaimsSet().getIssueTime());
System.out.println(jwt.getJWTClaimsSet().getJWTID());