JWE with 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.