Message-Level Encryption Using JSON Web Tokens
To use message-level encryption (MLE) with JSON Web Tokens (JWT), you must generate
the JWT and send it as part of the HTTP header. The payload is encrypted with the
dynamic Content Encryption key (CEK) that is generated for each transaction. The
serialized encrypted payload, the JWE, is passed as the request body.
- Use the required Maven dependency:<dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.0</version> </dependency>
- Prepare the API request payload. This example is hard-coded for demonstration.String jsonMsg = "{\"clientReferenceInformation\":{\"code\":\"TC50171_3\"},\"processingInformation\":{\"commerceIndicator\":\"internet\"},\"aggregatorInformation\":{\"subMerchant\":{\"cardAcceptorID\":\"1234567890\",\"country\":\"US\",\"phoneNumber\":\"650-432-0000\",\"address1\":\"900MetroCenter\",\"postalCode\":\"94404-2775\",\"locality\":\"FosterCity\",\"name\":\"VisaInc\",\"administrativeArea\":\"CA\",\"region\":\"PEN\",\"email\":\test@cybs.com\},\"name\":\"V-Internatio\",\"aggregatorID\":\"123456789\"},\"orderInformation\":{\"billTo\":{\"country\":\"US\",\"lastName\":\"VDP\",\"address2\":\"Address2\",\"address1\":\"201S.DivisionSt.\",\"postalCode\":\"48104-2201\",\"locality\":\"AnnArbor\",\"administrativeArea\":\"MI\",\"firstName\":\"RTS\",\"phoneNumber\":\"999999999\",\"district\":\"MI\",\"buildingNumber\":\"123\",\"company\":\"Visa\",\"email\":\test@cybs.com\},\"amountDetails\":{\"totalAmount\":\"102.21\",\"currency\":\"USD\"}},\"paymentInformation\":{\"card\":{\"expirationYear\":\"2031\",\"number\":\"5555555555554444\",\"securityCode\":\"123\",\"expirationMonth\":\"12\",\"type\":\"002\"}}}";
- Read the merchantp12file.The P12 file should have been created when you set up your test account. See Create a P12 File.ClassLoader classLoader = Main.class.getClassLoader(); KeyStore merchantKeyStore = KeyStore.getInstance("PKCS12", new BouncyCastleProvider()); merchantKeyStore.load(classLoader.getResourceAsStream("test_merchant.p12"), "test_merchant".toCharArray()); String merchantKeyAlias = null; Enumeration enumKeyStore = merchantKeyStore.aliases(); RSAPrivateKey rsaPrivateKey = null; RSAPrivateKey rsaPrivateKey_SJC = null; X509Certificate x509Certificate = null; X509Certificate x509Certificate_SJC = null;
- Loop through the Java KeyStore to extract the private key from merchantp12file and extract the public key forCybersource. You must use theCybersourceSJC (CyberSource_SJC_US) to encrypt the payload.while (enumKeyStore.hasMoreElements()) { merchantKeyAlias = (String) enumKeyStore.nextElement(); if (merchantKeyAlias.contains("test_merchant")) { KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) merchantKeyStore.getEntry( merchantKeyAlias, new KeyStore.PasswordProtection( "test_merchant".toCharArray())); //Extract the merchant certificate to sign the payload. x509Certificate = (X509Certificate) keyEntry.getCertificate(); rsaPrivateKey = (RSAPrivateKey) keyEntry.getPrivateKey(); //Extract the merchant certificate to encrypt the payload. } else if (merchantKeyAlias.contains("CyberSource_SJC_US")) { KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) merchantKeyStore.getEntry( merchantKeyAlias, new KeyStore.PasswordProtection( "test_merchant".toCharArray())); //Store the public key from the certificate. x509Certificate_SJC = (X509Certificate) keyEntry.getCertificate(); //rsaPrivateKey_SJC = (RSAPrivateKey) keyEntry.getPrivateKey(); } }
- Update the custom headers to include"iat"with the current timestamp:Map<String, Object> customHeaders = new HashMap<String, Object>(); customHeaders.put("iat", Instant.now().getEpochSecond());
- Generate the JWE token (the encrypted payload) using the supported algorithm and theCybersourcepublic certificate. Include the JSON payload as the input.String jweToken = encryptAttributeWithAlgo(jsonMsg, x509Certificate_SJC, JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM, customHeaders);public static String encryptAttributeWithAlgo(String content, X509Certificate x509Certificate, JWEAlgorithm algo, EncryptionMethod encryptionMethod, Map<String, Object> customHeaders) { if (isNullOrEmpty(content)) { System.out.println("empty or null content"); return null; } else if ( x509Certificate == null) { System.out.println("public certificate is null"); return null; } String serialNumber = extractSerialNumberFromDN(x509Certificate); JWEObject jweObject = new JWEObject( new JWEHeader.Builder(algo, encryptionMethod) .contentType("JWT") // required to signal nested JWT .keyID(serialNumber) .customParams(customHeaders) .build(), new Payload(content)); jweObject = encrypt(jweObject, x509Certificate); return jweObject == null ? null : serializeToken(jweObject); } public static boolean isNullOrEmpty(String string) { return (string == null || string.trim().length() == 0); }
- Build the JSON request body for calling theCybersourceAPI.String jsonBody = createJsonString(jweToken);private static String createJsonString(String jweToken) { String message; JSONObject json = new JSONObject(); json.put("encryptedRequest", jweToken); return json.toString(); }
- Generate the body digest to validate that the payload has not been compromised.String bodyDigest = createBodyDigest(jsonBody);public static String createBodyDigest(String jsonBody) { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance(DEFAULT_HASH_ALG); } catch (NoSuchAlgorithmException e) { System.out.println("Couldn't instantiate SHA-256 digest " + e.getMessage()); return null; } byte[] bodyDigestBytes = messageDigest.digest(jsonBody.getBytes()); return java.util.Base64.getEncoder().encodeToString(bodyDigestBytes); }
- Prepare the JWT payload for signature.JWTPayload jwtPayload = createJWTPayloadClass(bodyDigest); Map<String, Object> customHeader = new HashMap<String, Object>(); customHeader.put("v-c-merchant-id", "test_merchant");private static JWTPayload createJWTPayloadClass(String bodyDigest) throws NoSuchAlgorithmException { JWTPayload jwtPayload = new JWTPayload(); jwtPayload.setDigest(bodyDigest); jwtPayload.setDigestAlgorithm("SHA-256"); jwtPayload.setIat(String.valueOf(System.currentTimeMillis())); return jwtPayload; }
- Sign the payload and create the JWT token that is passed in the request header.String jwsSignature = sign(Json.encode(jwtPayload), rsaPrivateKey, x509Certificate, customHeader);public static String sign(String content, PrivateKey privateKey, X509Certificate x509Certificate, Map<String, ? extends Object> customHeaders) { return serializeToken(signPayload(content, privateKey, x509Certificate, customHeaders)); } protected static JOSEObject signPayload(String content, PrivateKey privateKey, X509Certificate x509Certificate, Map<String, ? extends Object> customHeaders) { return signPayload(content, privateKey, x509Certificate, customHeaders, true); } protected static JOSEObject signPayload(String content, PrivateKey privateKey, X509Certificate x509Certificate, Map<String, ? extends Object> customHeaders, boolean includeKid) { if (isNullOrEmpty(content) || x509Certificate == null || privateKey == null) { System.out.println("empty or null content or Private key or public certificate is null"); return null; } String serialNumber = extractSerialNumberFromDN(x509Certificate); List<Base64> x5cBase64List = addCertificateToBase64List(x509Certificate); if (x5cBase64List.isEmpty()) return null; RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey; Payload payload = new Payload(content); JWSHeader jwsHeader; if ( includeKid ) { jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256) .customParams((Map<String, Object>) customHeaders) .keyID(serialNumber) .x509CertChain(x5cBase64List) .build(); } else { jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256) .customParams((Map<String, Object>) customHeaders) .x509CertChain(x5cBase64List) .build(); } JWSObject jwsObject = new JWSObject(jwsHeader, payload); try { RSASSASigner signer = new RSASSASigner(rsaPrivateKey, true); jwsObject.sign(signer); if (!jwsObject.getState().equals(JWSObject.State.SIGNED)) { System.out.println("Payload signing failed."); return null; } } catch (JOSEException joseException) { System.out.println("ERROR_SIGN_AND_ENCRYPT_THE_PAYLOAD" + " " + joseException); return null; } return jwsObject; } protected static String extractSerialNumberFromDN(X509Certificate x509Certificate) { String serialNumber = null; String serialNumberPrefix = "SERIALNUMBER="; String principal = x509Certificate.getSubjectDN().getName().toUpperCase(); int beg = principal.indexOf(serialNumberPrefix); if (beg >= 0) { int end = principal.indexOf(",", beg); if (end == -1) end = principal.length(); serialNumber = principal.substring(beg + serialNumberPrefix.length(), end); } else serialNumber = x509Certificate.getSerialNumber().toString(); return serialNumber; }
- Send the JWT as the Bearer token in the header and send the JWE as the body.HTTP Request Header: Content-Type: application/json Authorization: Bearer eyJ2LWMtbWVyY2hhbnQtaWQiOiJtcG9zX3BheW1lbnRlY2giLCJhbGciOiJSUzI1NiIsImtpZCI6Im1wb3NfcGF5bWVudGVjaCIsIng1YyI6WyJNSUlDWmpDQ0FjK2dBd0lCQWdJV05qZ3hNek0wTkRNMk1qTXdNREU0TVRNNU5EY3lNekFOQmdrcWhraUc5dzBCQVFzRkFEQWVNUnd3R2dZRFZRUUREQk5EZVdKbGNsTnZkWEpqWlVObGNuUkJkWFJvTUI0WERUSXpNRFF4TWpJeE1qQXpObG9YRFRJMk1EUXhNakl4TWpBek5sb3dPekVZTUJZR0ExVUVBd3dQYlhCdmMxOXdZWGx0Wlc1MFpXTm9NUjh3SFFZRFZRUUZFeFkyT0RFek16UTBNell5TXpBd01UZ3hNemswTnpJek1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBdmdiWFRIS0pHaXNFM3JQVHZySjZvMXJIb1R0UDJPQWlhOEtPSEhSZ21uMFR1cU1MTjVndGNacVVtTGJVeWlIWGhrZGFqbUtVMG5tUGJpWWRuQW4yc2pDbDVvVUgrSGViXC9LVFE1NW1BVEJnY0ZOY0ZvNVdMREN4VzNEeFZvM1Q3U3BhTHRrU0ZtZWV1bUZpRzFnZ1hcL2h1SFI1QU5BK3dyS2R4VTk1WjFEZHBudUpTWkpjckg2cWRWXC9LdHlRNmxTRUJWUUhibXBvVmdtREVVdVNvUExDc0RJNVhSa0ZHOHZQRXZqRHNIQWdOeHpoMCt1RzJxN3p5SGRoTWFnS3IzOGdRandFZVBWZXY4c0ExYkJcL1VzaDNzSU5kdFhKREhBUFlPZWJTS25iTWh4a1dUZlg3TnRKRE1xaEQrXC9DK0hNWDRyTGxVQXZKSjVYMmJYWEtsUUdPeFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0R0JBQ0s3aWR5QWFQZTJGbDFITFhMaEo3RFBDMEtTTjFlRldZQUk4VDBqYmJmYWVHWXhkdXlOVitsRXlaUjF5MllWRWVjbU1xUllFN3RuV0JIYitoS0I1ODFTeEU4Z21ZQTVIaHA0VzdlczM2aDl1SUFvQ1JqU1pFWGJcL09wUG5VN0JkVDBMTlZ0N0hyR21JcnVtSlFmSDRvMitRbUY4T3BtM2thMnArRDRaaDNtTiJdfQ.ewogICAgICAgICAgICAiZGlnZXN0IjoiclU5ZUlUTHVTZzFpUEJlQW5WSGdNMmN4clBPQ1Rzais3cEhENWhIZFI2bz0iLAogICAgICAgICAgICAiZGlnZXN0QWxnb3JpdGhtIjoiU0hBLTI1NiIsCiAgICAgICAgICAgICJpYXQiOiIyMDIzLTA2LTA4VDIwOjQxOjI1LjYxNVoiCn0gCgo.RSOMFXouXVP-6_o_x0r6QRzMDfwr2wb0DG3EE48uKemG5oiHOmHQY_sN78bCbuFFgy1C1R8QZA-jphx0dH6jzuJxoerkgz3VSMuDt9mDJtnlDnisSTf35NWh6u4TeJqGr3E8oOOJSX6N32r6XovCXyJyaDm4h2fJOeLZc8HcvfSC5SpMUsC2vQMRbrRJXEfMjCiEbPplSuusfsmX3xbFTLnP1jlNY_hi2TZazFBb6rvetPpDM5kibRS7OpJoGWievmmDI6LyaelJhkghZ-_OwElAYZfOnZ9FphLQZWnwZ3mku0C6gysv6ISMrI9BlCpWaPbmDeuWBSLexC_U01cblg HTTP Request Body: {"encryptedRequest":"eyJ2LWMtbWVyY2hhbnQtaWQiOiJtcG9zX3BheW1lbnRlY2giLCJ4NWMiOlsiTUlJRE5UQ0NBaDJnQXdJQkFnSVdNVFkwT1RFNU1ERXhNREEwTkRBd056Y3lOakF6T0RBTkJna3Foa2lHOXcwQkFRc0ZBREJwTVFzd0NRWURWUVFHRXdKVlV6RU5NQXNHQTFVRUNnd0VWbWx6WVRFVU1CSUdBMVVFQ3d3TFEzbGlaWEpUYjNWeVkyVXhOVEF6QmdOVkJBTU1MRU41WW1WeVUyOTFjbU5sSUZSeVlXNXpZV04wYVc5dVlXd2dUbTl1VUhKdlpDQkpjM04xYVc1bklFTkJNQjRYRFRJeU1EUXdOVEl3TWpFMU1Wb1hEVEkxTURRd05USXdNakUxTVZvd1BqRWJNQmtHQTFVRUF3d1NRM2xpWlhKVGIzVnlZMlZmVTBwRFgxVlRNUjh3SFFZRFZRUUZFeFl4TmpRNU1Ua3dNVEV3TURRME1EQTNOekkyTURNNE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBeE53S1F5UXFJV2tCVWpLd041WEZyazgxdm5BSk9FOGVlUE80UENmckdETDdGeFJwS0NCWXN3MTk2MTB3MzlVMlFUU3hib3grMVhERUcxSVd3SGtTckJMeDVSVTQ3T2ZBeDEySHdcL3lNNCttUUVENERXSWFCVld6NmZRZDBJTG5tWTB5VDRkMjVWUTZ6dDRRYU1pcXMwM2x5d21WdGFvWEx3SDNuRjJzRzhXV1pEUjRqZ1J2ZkNnZzZVUWN6aVNQMzNRVENsMmxqUmYrTmtWRFJYNGFEOWFQblwvVWh2Qzlna1JBVVlXMUZCb0RMVVA0TTdrQ29WakVsZDgyZnVnMmNyNzNFTVlRbmtKUnpjT0xVcXk4T3lrQXNYWDBsTktKVVFINW1OTUlTWTVjXC8yVSs1UVVFSWRJalp1SFB0UkVxUklpb2tjdVlzRGlvUVRtQUxWSCtWdGR3SURBUUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDY2t0VytIczdXTGVDNGhBQTVvNmR6WXJvSXpcL2RnYXl0MWlDOVltUnNGRng1d3ZWV0pqeGM4aElDaXJHXC93QURWNjRxMDg3Y1ZobWJoM1wvZG52UEJwR1V6TWhnMUFsM1VBcFRDZTN0KzJhdENGR0lvWDZabk1aUEtKNnAxNEp5TTFUN2F2eEY0a3ZXaklQYVllbmJxWk5qcFBuTm1DREdselhZdjhwRXQ4ZWRZbGorT1QzS3N0VVV1eDZoRXI5MnJUekFGbVFXZlZxaURjYklZUG12XC9UWU0yNGxkUlwvMldUd3NpbzZwWUsxMk4yQUxxemJcL3U5MVE5MGEyN3NHbFNFV0RZcUo4aVwvM3Jidys5cDNkNXFlZzlXRmJ6N2FUUWVpOFJyU2pBRU1SRllDNnREUWEweStpUUgxZlNLaEg1YmJuOGdDbWJpbjNGaXJqWXFKVnVOZFplIl0sImtpZCI6IjE2NDkxOTAxMTAwNDQwMDc3MjYwMzgiLCJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiaWF0IjoxNjg2MjU2NjkyLCJhbGciOiJSU0EtT0FFUCJ9.YyXUcX_fzTGwhfAon6gT62rrZWIybdgAH2FLhtJxQin0hu0OaWvYYT34bLguXqbEOxzXxYRcOo-GCEaFs15Ul_BrtlhQTn9aKjX_-rbYxM-ZXJlbpg6CsyAqy63-MkYPP2BNXjFfP3yUSxes76zHlMaJG0gp681QY85AqGq6mCSrDqWE7NUTWifseRtKMv5u9pMHMxddkz9Xvp6Q5TbiEjGZbvD-xKhhgs0-IupvPDKhxdJSNVPaDiTnFVnYtyOuLZLOFO4Fq2bfj86iGHRjfh9zq91Gp4uN36kmRHzkLN4Wrr5R6D79Z-FC5bLU4BUrilGQtVSWCWtcxYAIQOhz1w.tuv-9Xtl0uNoPxXV.RRGnkA1chplnGQf-SlXaXEntzGJrEF4EJU-F6PEx6H3us1APoWAR-26aHdWctNFoGSalNt1ZzidRi3TA-iwpSFkEonSVbe7aVLJeAKgqCHnVXT-eWb89gqTVkQFZiSZCHtIjDUtOMy95sU4MRcCvtrfAPDnIMudVVA5YtAsCZpta_ATl_iS6oLBMI57R0Ra7pO3MxFdLTrk-FkLSd4JbGokm_JXpH8lI1V11vaMAtyEqGrzllrQv408zUGbvtvSirF31iiGITEF7QG5rbVn7oTWF4wWzKEkpSZ7J4LpIdjCG6sojeld4XkK9dHHL1r0-vFVfa-ua4uh4PNcVK0o3ke4TOqLnVcnaEtYW1AS2wIu_tHxW_hdkyPmDI8ceSBqmloRxV3q8xOS5u-2GNQ9p5pm2_NjkqVB8RYup9NFZWBBjriLRaMTp41W5T_gOOQkH8Xt2JaKAxwevrtg0UST3rryjt5U9y074DJqDZAS1OOjoCHaQga4S34L5gJTPIPRe94G_jBU1o9SmGtrHMDTkxL5-RzJ0mOwPF2MZzQ318z78IyAGXotYT4QXGJZhnyDMNgHjyyGX7IZtGPRYDpxc10Kko9DLM_r6fWoDLemRhFbi8prnlJpQZbUh98TLRwCndmH9lIdyH6v3YUfFde_ncxYPMbkDqEdoi26bvm1pWRFm8EI6kXGZV_H9bCcse2x3JZFzUg0xp5yv0bfSAxdjKOqmRqIR.5ooQUzqPfFtPjGCefMZ_6A"}