I am trying to implement ECDH encryption/decryption along with JWE in Android (Java).
I have found the jose4j and Nimbus JOSE libraries that aim to do everything I need but appears that it's more challenging than I thought.
If anybody is familiar then it's for 3D Secure 2.0...
In the spec below:
- SDK = the local side
- DS = Directory Server (the other side)
Next is the spec:
- Given: P(DS) - an EC public key (provided in PEM format, can be transformed to PublicKey or to JWK)
- Generate a fresh ephemeral key pair (Q(SDK), d(SDK))
- Conduct a Diffie-Hellman key exchange process according to JWA (RFC7518) in Direct Key Agreement mode using curve P-256, d(SDK) and P(DS) to produce a CEK. The parameter values supported in this version of the specification are:
- "alg":ECDH-ES
- "apv":DirectoryServerID
- "epk":P(DS),inJSONWebKey(JWK)format {"kty":"EC", "crv":"P-256"}
- All other parameters: not present
- CEK:"kty":oct-256bits
- Generate 128-bit random data as IV
- Encrypt the JSON object according to JWE (RFC7516) using the CEK and JWE Compact Serialization. The parameter values supported in this version of the specification are:
- "alg":dir
- "epk":Q(SDK) as {"kty": "EC", "crv": "P-256"}
- "enc":either"A128CBC-HS256"or"A128GCM"
- All other parameters: not present
- If the algorithm is A128CBC-HS256 use the full CEK or if the algorithm is A128GCM use the leftmost 128 bits of the CEK.
- Delete the ephemeral key pair (Q(SDK),d(SDK))
- Makes the resulting JWE available to the 3DS Server as SDK Encrypted Data
If someone has implemented this exact spec and can share the code this would be brilliant!!
There's an example of creating JWT using ECDH in the examples of jose4j:
https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples (the last example, titled as "Producing and consuming a nested (signed and encrypted) JWT").
But this example is not exactly what I need. It creates a token while I need to encrypt a text.
Starting from "CEK:"kty":oct-256bits" in the spec above I don't understand what to do.
Here's my code (so far) using Nimbus lib:
public String nimbus_encrypt(String plainJson, ECPublicKey otherPublicKey, String directoryServerId) throws JOSEException {
JWEHeader jweHeader = new JWEHeader(
JWEAlgorithm.ECDH_ES,
EncryptionMethod.A128CBC_HS256,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Base64URL.encode(directoryServerId),
null,
0,
null,
null,
null,
null);
JWEObject jwe = new JWEObject(jweHeader, new Payload(plainJson));
jwe.encrypt(new ECDHEncrypter(otherPublicKey));
String serializedJwe = jwe.serialize();
Log.d("[ENCRYPTION]", "nimbus_encrypt: jwe = " + jwe.getHeader());
Log.d("[ENCRYPTION]", "nimbus_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
This is the nimbus output:
nimbus_encrypt: jwe = {"epk":{"kty":"EC","crv":"P-256","x":"AS0GRfAOWIDONXxaPR_4IuNHcDIUJPHbACjG5L7x-nQ","y":"xonFn1vRASKUTdCkFTwsl16LRmSe-bAF8EO4-mh1NYw"},"apv":"RjAwMDAwMDAwMQ","enc":"A128CBC-HS256","alg":"ECDH-ES"}
nimbus_encrypt: serializedJwe = eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJBUzBHUmZBT1dJRE9OWHhhUFJfNEl1TkhjRElVSlBIYkFDakc1TDd4LW5RIiwieSI6InhvbkZuMXZSQVNLVVRkQ2tGVHdzbDE2TFJtU2UtYkFGOEVPNC1taDFOWXcifSwiYXB2IjoiUmpBd01EQXdNREF3TVEiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiRUNESC1FUyJ9..Pi48b7uj3UilvVXKewFacg.0sx9OkHxxtZvkVm-IENRFw.bu5GvOAwcZxdxaDKWIBqwA
Here's my code (so far, using @Brian-Campbell's answer) using jose4j lib:
public String jose4j_encrypt(String plainJson, PublicKey otherPublicKey, String directoryServerId) throws JoseException {
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation(directoryServerId));
jwe.setKey(otherPublicKey);
jwe.setPayload(plainJson);
String serializedJwe = jwe.getCompactSerialization();
Log.d("[ENCRYPTION]", "jose4j_encrypt: jwe = " + jwe);
Log.d("[ENCRYPTION]", "jose4j_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
This is the jose4j output:
jose4j_encrypt: jwe = JsonWebEncryption{"alg":"ECDH-ES","enc":"A128CBC-HS256","apv":"RjAwMDAwMDAwMQ","epk":{"kty":"EC","x":"prvyhexJXDWvPQmPA1xBjY8mkHEbrEiJ4Dr-7_5YfdQ","y":"fPjw8UdfzgkVTppPSN5o_wprItKLwecoia9yrWi38yo","crv":"P-256"}}
jose4j_encrypt: serializedJwe = eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImFwdiI6IlJqQXdNREF3TURBd01RIiwiZXBrIjp7Imt0eSI6IkVDIiwieCI6InBydnloZXhKWERXdlBRbVBBMXhCalk4bWtIRWJyRWlKNERyLTdfNVlmZFEiLCJ5IjoiZlBqdzhVZGZ6Z2tWVHBwUFNONW9fd3BySXRLTHdlY29pYTl5cldpMzh5byIsImNydiI6IlAtMjU2In19..gxWYwFQSOqLk5HAgs7acdA.mUIHBiWpWSlQaEOJ_EZGYA.eiTe-88fw-Jfuhji_W0rtg
As can be seen the "alg" header in the final result is "ECDH-ES" and not "dir" as required.
If I would implement both sides of the communication then it would be enough, but with this spec seems like many configurations are missing here...
Code using jose4j is longer and seems more configurable but I couldn't construct something valuable enough to post here.
The main missing part for me is how to generate the CEK from the spec above.
Thank you.
EDIT
Added jose4j code above and added the outputs...