17

I am trying to implement Diffie-Hellman key exchange in Java, but I'm having a hard time understanding the specification:

Complete the Diffie-Hellman key exchange process as a local mechanism according to JWA (RFC 7518) in Direct Key Agreement mode using curve P-256, dT and QC to produce a pair of CEKs (one for each direction) which are identified by Transaction ID. The parameter values supported in this version of the specification are:

  • “alg”: ECDH-ES
  • “apv”: SDK Reference Number
  • “epk”: QC, in JSON Web Key (JWK) format
  • {“kty”:”EC” “crv”:“P-256”}
  • All other parameters: not present
  • CEK: “kty”:oct - 256 bits

Create a JSON object of the following data as the JWS payload to be signed:

{“MyPublicKey”: “QT”, “SDKPublicKey”:” QC”}

Generate a digital signature of the full JSON object according to JWS (RFC 7515) using JWS Compact Serialization. The parameter values supported in this version of the specification are:

  • “alg”: PS256 or ES256
  • “x5c”: X.5C v3: Cert(MyPb) plus optionally chaining certificates

From my understanding, ECDH will produce a secret key. After sharing my ephemeral public key (QT), the SDK produces the same secret key, so we can later exchange JWE messages encrypted with the same secret key.

The JSON {“MyPublicKey”: “QT”, “SDKPublicKey”:” QC”} will be signed and sent, but I do not understand how I will use apv and epk since these header params are used in JWE and not in the first JWS to be shared.

On the same specification, they talk about these JWE messages, but they do not have these apv and epk parameters.

Encrypt the JSON object according to JWE (RFC 7516) using the same “enc” algorithm used by the SDK, the CEK obtained identified by “kid” and JWE Compact Serialization. The parameter values supported in this version of the specification are:

  • “alg”: dir
  • “enc”: either A128CBC-HS256 or A128GCM
  • “kid”: Transaction ID
  • All other parameters: not present

I also read the example in RFC 7518 where I can see the header params apv and epk being used but I'm not sure which header params, JWE's or JWS's ?

Any thought on how this could be implemented using nimbus-jose-jwt or any other java library would be really helpful. Thanks

Neil
  • 997
  • 9
  • 24
Noureddine AMRI
  • 2,902
  • 1
  • 19
  • 27
  • 1
    Please correct me if i am wrong. Consider user1 and user 2. Step 1. You want to get ephemeral key agreement and exchange keys using ECDH as a key secure key exchange algorithm between user1 and user2. Step 2. Then user1 wants to send messages which are secured using JSON Web Signature (JWS) with his EC pub and pri key which could then be verfied by user2 and then get the message. – Shubham Kadlag May 29 '18 at 11:59
  • @Noureddine how did you implement it eventually? – Michael Kessler Sep 12 '19 at 13:36

3 Answers3

6

Both apv (Agreement PartyVInfo) and epk (Ephemeral Public Key) are optional, so they can be used in multiple ways. You can use apv to reflect SDK version for example. They are added to the JWE header.

You can read more about JWE in Nimbus here

An example using Nimbus JOSE would be:

import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;

public class Security {

    public void generateJWE() throws JOSEException, URISyntaxException {
        JWEHeader jweHeader = buildJWEHeader(new Base64URL("202333517"), buildECKey());
        JWEObject jweObject = new JWEObject(jweHeader, new Payload("Hello World!"));
    }

    private JWEHeader buildJWEHeader(Base64URL apv, ECKey epk) {
        JWEHeader.Builder jweBuilder = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A128GCM);
        jweBuilder.agreementPartyVInfo(apv);
        jweBuilder.ephemeralPublicKey(epk);
        return jweBuilder.build();
    }

    private ECKey buildECKey() throws URISyntaxException {
        Set<KeyOperation> keyOperations = new HashSet<>();
        keyOperations.add(KeyOperation.ENCRYPT);
        String transactionID = "73024831";
        URI x5u = new URI("https//website.certificate");
        KeyStore keystore = null; //initialize it
        List<Base64> x5c = new ArrayList<>();
        return new ECKey(Curve.P_256, new Base64URL("x"), new Base64URL("y"), KeyUse.ENCRYPTION, keyOperations, Algorithm.NONE, transactionID, x5u, new Base64URL("x5t"), new Base64URL("x5t256"), x5c, keystore);
    }
}

Instead of EncryptionMethod.A128GCM you can use EncryptionMethod.A128CBC-HS256 as in your specification. apv and epk are added to JWEHeader inside builder. Other parameters can be chosen in constructors of JWEHeader.Builder and ECKey. I used ECDH-ES algorithm, A128GCM encryption method, P-256 curve (elliptic curve is default in ECKey generation), transaction ID is a string. I chose other parameters without any clear pattern. Initialization of KeyStore would be too broad for the example. Encryption is only one thing you can do with JWE, among signature and others.

banan3'14
  • 2,096
  • 1
  • 14
  • 35
2

Nimbus (as well as Jose4j) is not the best choice for implementing the specification (I guess it's 3D secure 2.x.x).

These libraries do not return content encrypion key but use it to encrypt or decrypt messages as per JWE spec.

I found that Apache CXF Jose library does its job well as a JWE/JWS/JWK implementation. Except generating ephemeral key pairs. But it's can easily be done with Bouncy Castle:

Security.addProvider(new BouncyCastleProvider());
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec(JsonWebKey.EC_CURVE_P256);
KeyPairGenerator g = KeyPairGenerator.getInstance(ALGORITHM_SIGNATURE_EC, "BC");
g.initialize(ecGenSpec, new SecureRandom());
KeyPair keyPair = g.generateKeyPair();

Content encrypion key can be produced with this code:

byte[] cek = JweUtils.getECDHKey(privateKey, publicKey, null, SDKReferenceNumber, "", 256);

And used to encrypt messages getting JWE compact representation:

JweEncryption jweEncryption = JweUtils.getDirectKeyJweEncryption(cek, contentAlgorithm);
JweHeaders head = new JweHeaders();
head.setHeader(JoseConstants.HEADER_KEY_ID, keyId);
String encrypted = jweEncryption.encrypt(contentToEncrypt.getBytes(StandardCharsets.UTF_8), head);

Or decrypt:

JweCompactConsumer compactConsumer = new JweCompactConsumer(encrypted);
JweHeaders head = compactConsumer.getJweHeaders();
JweDecryption jweDecryption = JweUtils.getDirectKeyJweDecryption(cek, head.getContentEncryptionAlgorithm());
JweDecryptionOutput out = jweDecryption.decrypt(encrypted);
String decrypted = out.getContentText();
Argb32
  • 1,167
  • 7
  • 10
  • what is the publicKey while generating cek? Is it sdkPublicKey? – Pand005 Apr 24 '20 at 06:13
  • I used below code foe cek but got error - byte[] cek = JweUtils.getECDHKey(privateKey,sdkEphemeralPublicKey.createEcPublicKey(),null, sdkReferenceNumber.getBytes(StandardCharsets.UTF_8),"ECDH-ES",256); Error: AM org.apache.cxf.rs.security.jose.jwe.AesCbcHmacJweDecryption validateAuthenticationTag WARNING: Invalid authentication tag – Pand005 Apr 24 '20 at 06:52
  • Argb32 can u share . – Pand005 Apr 24 '20 at 15:21
  • @Pand005 It's unclear where you are getting this error. During decryption I guess? If you writing ACS you should use ACS private key and public key received from SDK. If you are writing 3DS server you should use it's private key and public key from ACS – Argb32 Apr 24 '20 at 18:18
  • @Argb32- Yes, we are decrypting in ACS and at the time of generating CEK we are using ACS privateKey & SDK publicKey. But, seeing above error. I see you are not using Alg name "ECDH-ES" but we are using, without using alg name for generating CEK it's invalid alg exception. We are using "cxf-rt-rs-security-jose" 3.1.7. – Pand005 Apr 24 '20 at 19:48
  • byte[] cek = JweUtils.getECDHKey(privateKey, sdkEphemeralPublicKey.createEcPublicKey(), null, sdkReferenceNumber.getBytes(), "", 256); ERROR: Caused by: java.lang.SecurityException: java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/CBC/PKCS7Padding Anything wrong here? – Pand005 Apr 24 '20 at 19:56
  • @Pand005 public key parameter in getECDHKey() is a JsonWebKey created from SDK ephemeral key. As of NoSuchAlgorithmException - it works for me. Try add BouncyCastle. Also try security-jose version 3.2.6 as I remember problems with earlier versions. – Argb32 Apr 28 '20 at 15:54
  • Security.addProvider(new BouncyCastleProvider()); Solved my problem. – Pand005 Apr 30 '20 at 13:45
  • @Pand005 / @Argb32 what is `keyId` in this code snippet? I am trying to implement ECDH encryption in 3DS Android SDK (this is the last test case left before certification - jose4j and Nimbus both fail to encrypt the device info using ECDH according to EMVCo specs). – Michael Kessler Jun 03 '20 at 08:45
  • 1
    Ok, got it. This question is about CReq/CRes encryption, while I thought it's about device info encryption... – Michael Kessler Jun 03 '20 at 10:38
0

I able to generate the Secret Key for A128CBC-HS256. However still not succeed in A128GCM. Adding the working sample for A128CBC-HS256 with help nimbus-jose library. This below code is only for key generation.

Please also note that I override the nimbus-jose classes for my usage. Hope it helps.

private void generateSHA256SecretKey(AREQ areq, ECKey sdkPubKey, KeyPair acsKeyPair) throws Exception {

        // Step 4 - Perform KeyAgreement and derive SecretKey
        SecretKey Z = CustomECDH.deriveSharedSecret(sdkPubKey.toECPublicKey(), (ECPrivateKey)acsKeyPair.getPrivate(), null);

        CustomConcatKDF concatKDF = new CustomConcatKDF("SHA-256");

        String algIdString = "";
        String partyVInfoString = areq.getSdkReferenceNumber();
        int keylength = 256; //A128CBC-HS256            

        byte[] algID = CustomConcatKDF.encodeDataWithLength(algIdString.getBytes(StandardCharsets.UTF_8));
        byte[] partyUInfo = CustomConcatKDF.encodeDataWithLength(new byte[0]);          
        byte[] partyVInfo = CustomConcatKDF.encodeDataWithLength(partyVInfoString.getBytes(StandardCharsets.UTF_8));
        byte[] suppPubInfo = CustomConcatKDF.encodeIntData(keylength);
        byte[] suppPrivInfo = CustomConcatKDF.encodeNoData();

        SecretKey derivedKey = concatKDF.deriveKey(
            Z,
            keylength,
            algID,
            partyUInfo,
            partyVInfo,
            suppPubInfo,
            suppPrivInfo);

        System.out.println("Generated SHA256 DerivedKey : "+SecureUtils.bytesToHex(derivedKey.getEncoded()));       
    }
Ashish Kirpan
  • 109
  • 1
  • 3
  • could you please share what exactly did you override in Nimbus and what was the change? I am trying to implement ECDH encryption in 3DS Android SDK (this is the last test case left before certification - jose4j and Nimbus both fail to encrypt the device info using ECDH according to EMVCo specs). – Michael Kessler Jun 03 '20 at 08:48