JSON Web Encryption (JWE) to multiple recipients

The general JSON serialisation of JWE supports encryption of a plaintext to multiple recipients, expressed in a single JSON object.

The encryption keys

Each recipient must provide the sender with its public encryption key.

The public key must be in JWK format and specify:

  • The encryption algorithm for the recipient in the JWK alg parameter.

  • A JWK parameter, typically kid (key ID), which will be automatically included in the per-recipient unprotected header and which the recipient will use to locate its JWE recipients object. Alternative or additional supported header parameters to identify the public key are x5t, x5t#S256, x5c and x5u.

Example public encryption elliptic curve JWK for a recipient, specifying the ECDH-ES+A128KW JWE algorithm and using a kid parameter to identify the key:

{
  "kty" : "EC",
  "crv" : "P-256",
  "use" : "enc",
  "alg" : "ECDH-ES+A128KW",
  "kid" : "rOSm9Td1lFIp9oiPuS3IqTpAZzIzEFUSc2fpK4nnymM",
  "x"   : "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
  "y"   : "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"
}

A key like the above can be generated like this:

import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;

// Generate EC key pair in JWK format
ECKey jwk = new ECKeyGenerator(Curve.P_256)
    .keyUse(KeyUse.ENCRYPTION)
    .algorithm(JWEAlgorithm.ECDH_ES_A128KW)
    .keyIDFromThumbprint(true)
    .generate();

// Save the public + private EC JWK for the recipient
System.out.println(jwk);

// Output the public EC JWK for the sender
System.out.println(jwk.toPublicJWK());

How to encrypt to multiple recipients

Prior to encryption, the sender must obtain the public key for each recipient, in JWK format. Each JWK must specify an alg (algorithm) and a key identifying parameter, as explained above.

The JSON formatted JWE is composed by specifying a JWE protected header with the desired content encryption method (enc) and setting the plaintext. The JWE algorithm (alg) will be obtained from the alg parameter of the recipient's JWK.

The JWEObjectJSON is then encrypted with a MultiEncrypter configured with the recipients' keys.

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import java.util.*;

// The public keys for all recipients must be put in a JWK set
JWKSet jwkSet = new JWKSet(
    Arrays.asList(
        recipient1Key,
        recipient2Key,
        recipient3Key));

// Compose the JWE JSON object
JWEObjectJSON jwe = new JWEObjectJSON(
    new JWEHeader(EncryptionMethod.A128GCM),
    new Payload("Hello, world!"));

// Create a JWE encrypter configured with the recipients' keys
JWEEncrypter encrypter = new MultiEncrypter(jwkSet);

// Perform the encryption
jwe.encrypt(encrypter);

// Serialise the JWE to the general JSON form
String jweJSONString = jwe.serializeGeneral();

How to decrypt

Upon receiving the serialised string a recipient must first parse it and then decrypt it with a MultiDecrypter configured with its private JWK.

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import java.util.*;

// The saved private JWK
JWK privateJWK = ...;

// Parse the JWE JSON string
JWEObjectJSON jwe = JWEObjectJSON.parse(jweJSONString);

// Create a JWE decrypter configured with the private key
JWEDecrypter decrypter = new MultiDecrypter(privateJWK);

// Perform the decryption
try {
    jwe.decrypt(decrypter);
} catch (JOSEException e) {
    System.err.println("Decryption failed: " + e.getMessage());
    return;
}

// Get the decrypted payload as a string
String payload = jwe.getPayload().toString();

Tips

Identical JWE algorithm for all recipients

When the recipients use the same JWE algorithm for the key encryption, e.g. RSA-OAEP-256, the JWEObjectJSON can be constructed like this:

JWEObjectJSON jwe = new JWEObjectJSON(
    new JWEHeader(
        JWEAlgorithm.RSA_OAEP_256, // alg shared by all recipients
        EncryptionMethod.A128GCM
    ),
    new Payload("Hello, world!")
);

If one or more of the supplied public JWKs for the recipients has a different alg (algorithm) parameter than the one specified in the JWEHeader a JOSEException will be thrown at encryption time.

The public JWK IDs should be globally unique

When a recipient generates an encryption JWK and chooses to assign a kid (key ID) to it, the identifier string should be globally unique to prevent a clash with the value of another JWK and potential decryption errors. This means schemes like number sequences (1, 2, 3, ...) for the kid should be avoided.

A suitable JWK kid scheme is to use UUIDs or the JWK thumbprint value:

ECKey jwk = new ECKeyGenerator(Curve.P_256)
    .keyUse(KeyUse.ENCRYPTION)
    .algorithm(JWEAlgorithm.ECDH_ES_A128KW)
    .keyIDFromThumbprint(true) // compute the SHA-256 thumbprint and use as kid
    .generate();

Application protocols and multi-recipient JWE senders can choose to enforce kid uniqueness at encryption time.