Step 4: Enable Message-Level Encryption

IMPORTANT
This feature is in the pilot phase. To use message-level encryption, contact your sales representative.
There are additional tasks you must complete before you can enable message-level encryption. For more information, see Prerequisites for MLE.
Message-Level Encryption (MLE) enables you to store information or communicate with other parties while helping to prevent uninvolved parties from understanding the stored information. MLE is optional and supported only for payments services.
MLE provides enhanced security for message payload by using an asymmetric encryption technique (public-key cryptography). The message encryption is implemented with symmetric encryption using Advanced Encryption Standard (AES), Galois Counter Mode (GCM) with 256-bit key size. The encryption of keys is supported using RSA Optimal Asymmetric Encryption Padding (OAEP) with 2048-bit key size. The encryption service is based on JSON Web Encryption (JWE), works on top of SSL and requires separate key-pairs for request and response legs of the transaction.
MLE is required for APIs that primarily deal with sensitive transaction data, both financial and non-financial. These are the types of sensitive transaction data:
  • Personal identification information (PII)
  • Personal account number (PAN)
  • Personal account information (PAI)
MLE is supported when using JSON web tokens.
Each of these authentication schemes uses an encrypted payload, called the
JWE
. A JWE token has these five components, with each component separated by a period (.):
  • JOSE header containing four elements:
    "alg": "RSA-OAEP-256", // The algorithm used to encrypt the CEK. "enc": "A256GCM", // The algorithm used to encrypt the message. "iat": "
    1702493653
    ", // The current timestamp in milliseconds. "kid": "
    keyId
    " // The serial number of shared public cert for encryption of CEK.
  • JWE encrypted key
  • JWE initialization vector
  • JWE additional authentication data (AAD)
  • JWE ciphertext and authentication tag

Prerequisites for MLE

Before enabling message-level encryption (MLE), you must complete these requirements:
  1. Sign the pilot agreement for using MLE.
  2. Confirm that the APIs you are integrating to support MLE.
  3. Retrieve the
    Cybersource
    public key from either the Account Manager or Client Executive services in the
    Business Center
    .
  4. Ensure that your system is configured to read the public key and encrypt the API payload.

Overview of MLE Set Up Tasks

Use the information in this section to configure your system with your own custom MLE using JWTs.
If you do not want to set up your own custom MLE, you can use the REST Client SDK instead. For more information, see the REST Client SDKs in GitHub.

Figure:

Overview of MLE Set Up Tasks
  1. Import the required programming libraries for your system.
  2. Import these three certificates:
    • Signing certificate (REST – Certificate).
    • MLE request certificate (SJC public certificate)
    • MLE response certificate (REST – API response MLE)
  3. Encrypt the JSON in your request using a JSON Web Encryption (JWE) that uses the SJC public certificate.
  4. Create the HTTP body in the format of
    {"encryptedRequest": "
    JWE-with-SJC
    "}
    .
  5. Create the JSON Web Signature (JWS) payload with these JWT payload fields and your signing certificate's private key:
    • iat
    • v-c-api-response-kid
      from the MLE response certificate.
    • digestAlgorithm
    • digest
      of the HTTP body
  6. Sign the JWS with your signing certificate and send it as
    Authorization: Bearer
    , such as a P12 certificate.
  7. Receive an encrypted response and decrypt it with the MLE private key. You will receive the response in this format:
    {"encryptedResponse": "
    JWE-with-ResponseMLECertificate
    "}
    The JWE contains a JOSE header containing these four default elements:
    "alg": "RSA-OAEP-256", // The algorithm used to encrypt the CEK. "enc": "A256GCM", // The algorithm used to encrypt the message. "iat": "
    1702493653
    ", // The current timestamp in milliseconds. "kid": "
    keyId
    " // The serial number of the
    v-c-api-response-kid
    from the authentication JWS in step 5.
    These are the supported algorithms you can set the
    alg
    element to:
    • RS256: SHA256withRSA
    • RS384: SHA384withRSA
    • RS512: SHA512withRSA
    • PS256: RSASSA-PSS
    • PS256: SHA256withRSAandMGF1
    • PS384: RSASSA-PSS SHA-384
    • PS384: SHA384withRSAandMGF1
    • PS512: RSASSA-PSS SHA512
    • PS512: SHA512withRSAandMGF1

Java Example: Enabling MLE Using JWTs

The below steps are examples of a way in which you can configure your system to create a custom MLE with JWTs. These example steps use the Java programming language.
  1. Import your preferred libraries to support MLE. In this example, the configuration uses Java leveraging the open source Nimbus JOSE and Bouncy Castle libraries.
    // Nimbus JOSE + JWT import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWEHeader; import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.Payload; import com.nimbusds.jose.crypto.RSADecrypter; import com.nimbusds.jose.crypto.RSAEncrypter; import com.nimbusds.jose.crypto.RSASSASigner; // BouncyCastle (PEM parsing + cert conversion) import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
  2. Import the signing, MLE, and SJC certificates. This example uses the P12 certificate as the signing certificate.
    public final class KeyPairMaterial { public final PrivateKey privateKey; public final X509Certificate cert; public KeyPairMaterial(PrivateKey k, X509Certificate c) { this.privateKey = k; this.cert = c; } } public final class CryptoMaterialDual { // Merchant: JWS (REST – Certificate) public final KeyPairMaterial signingCert; // Merchant: Response decryption (API Response MLE) public final KeyPairMaterial responseCert; // Platform encryption cert (SJC) public final X509Certificate sjcCert; public CryptoMaterialDual(KeyPairMaterial signingCert, KeyPairMaterial responseCert, X509Certificate sjcCert) { this.signingCert = signingCert; this.responseCert = responseCert; this.sjcCert = sjcCert; } }
  3. Unpack your imported certificates into a usable format for your system.
    Create this method for your system to read your P12 file, if you are using the P12 certificate.
    static KeyPairMaterial loadKeyPairFromP12(Path p12Path, char[] password, String keyAlias) throws Exception { KeyStore ks = KeyStore.getInstance("PKCS12"); try (InputStream in = Files.newInputStream(p12Path)) { ks.load(in, password); } KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) ks.getEntry( keyAlias, new KeyStore.PasswordProtection(password)); return new KeyPairMaterial(entry.getPrivateKey(), (X509Certificate) entry.getCertificate()); }
    Create this method for your system to read the PEM chain and private key.
    static KeyPairMaterial loadKeyPairFromPem(Path certificateChainPem, String privateKeyPem) throws Exception { X509Certificate leaf = readPemCerts(certificateChainPem).get(0); PrivateKey key = readPkcs8PrivateKey(privateKeyPem); return new KeyPairMaterial(key, leaf); }
    Create this method for your system to read the SJC from the P12 file or PEM chain.
    static X509Certificate loadSjcFromP12(Path p12Path, char[] password, String sjcAlias) throws Exception { KeyStore ks = KeyStore.getInstance("PKCS12"); try (InputStream in = Files.newInputStream(p12Path)) { ks.load(in, password); } return (X509Certificate) ks.getCertificate(sjcAlias); } static X509Certificate loadSjcFromPem(Path sjcCertPem) throws Exception { return readPemCerts(sjcCertPem).get(0); }
    Create this method to include PEM helper functions.
    static List<X509Certificate> readPemCerts(Path pemPath) throws Exception { try (Reader r = Files.newBufferedReader(pemPath); org.bouncycastle.openssl.PEMParser p = new org.bouncycastle.openssl.PEMParser(r)) { var xconv = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter().setProvider("BC"); List<X509Certificate> certs = new ArrayList<>(); Object o; while ((o = p.readObject()) != null) { if (o instanceof org.bouncycastle.cert.X509CertificateHolder h) certs.add(xconv.getCertificate(h)); } return certs; } } static PrivateKey readPkcs8PrivateKey(String pem) throws Exception { try (var parser = new org.bouncycastle.openssl.PEMParser(new StringReader(pem))) { Object o = parser.readObject(); var conv = new org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter().setProvider("BC"); if (o instanceof org.bouncycastle.asn1.pkcs.PrivateKeyInfo pki) return conv.getPrivateKey(pki); if (o instanceof org.bouncycastle.openssl.PEMKeyPair kp) return conv.getPrivateKey(kp.getPrivateKeyInfo()); throw new IllegalArgumentException("Expect PKCS#8 private key PEM"); } }
  4. Create these methods as helpers for encrypting and signing.
    static String kidFromCert(X509Certificate cert) { String dn = cert.getSubjectDN().getName().toUpperCase(); int i = dn.indexOf("SERIALNUMBER="); if (i >= 0) { int j = dn.indexOf(",", i); if (j < 0) j = dn.length(); return dn.substring(i + "SERIALNUMBER=".length(), j).trim(); } return cert.getSerialNumber().toString(); } static String encryptToJwe(String json, X509Certificate sjcCert) throws Exception { var header = new com.nimbusds.jose.JWEHeader.Builder( com.nimbusds.jose.JWEAlgorithm.RSA_OAEP, com.nimbusds.jose.EncryptionMethod.A256GCM) .contentType("JWT") .keyID(kidFromCert(sjcCert)) .build(); var jwe = new com.nimbusds.jose.JWEObject(header, new com.nimbusds.jose.Payload(json)); jwe.encrypt(new com.nimbusds.jose.crypto.RSAEncrypter((RSAPublicKey) sjcCert.getPublicKey())); return jwe.serialize(); } static String sha256Base64(String body) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); return Base64.getEncoder().encodeToString(md.digest(body.getBytes(StandardCharsets.UTF_8))); } static String signAsJws(String payload, KeyPairMaterial signingCert) throws Exception { var header = new com.nimbusds.jose.JWSHeader.Builder(com.nimbusds.jose.JWSAlgorithm.RS256) .keyID(kidFromCert(signingCert.cert)) .type(com.nimbusds.jose.JOSEObjectType.JWT) // typ=JWT .build(); var jws = new com.nimbusds.jose.JWSObject(header, new com.nimbusds.jose.Payload(payload)); jws.sign(new com.nimbusds.jose.crypto.RSASSASigner(signingCert.privateKey)); return jws.serialize(); } static String decryptJwe(String compactJwe, KeyPairMaterial responseCert) throws Exception { var jwe = com.nimbusds.jose.JWEObject.parse(compactJwe); jwe.decrypt(new com.nimbusds.jose.crypto.RSADecrypter((RSAPrivateKey) responseCert.privateKey)); return jwe.getPayload().toString(); }
  5. After setting up the above methods in your system, create a class that uses the methods to encrypt and decrypt your payloads with MLE using JWTs.
    // Example mix: // - REST – Certificate from PKCS#12 // - API Response MLE from PEM // - SJC from PEM KeyPairMaterial signingCert = loadKeyPairFromP12( Paths.get("merchant.p12"), "password".toCharArray(), "merchant"); KeyPairMaterial responseCert = loadKeyPairFromPem( Paths.get("api_response_mle_chain.pem"), Files.readString(Paths.get("api_response_mle_private_key.pem"))); X509Certificate sjc = loadSjcFromPem(Paths.get("sjc_certificate.pem")); CryptoMaterialDual mat = new CryptoMaterialDual(signingCert, responseCert, sjc); // 1) Build your request JSON String requestJson = new org.json.JSONObject() .put("amount", "10.00") .put("currency", "USD") .put("reference", "ORDER-12345") .toString(); // 2) Encrypt request body to JWE using SJC public cert String encryptedJwe = encryptToJwe(requestJson, mat.sjcCert); // 3) Build the HTTP body (this is what you’ll hash for the digest) String httpBody = new org.json.JSONObject() .put("encryptedRequest", encryptedJwe) .toString(); // 4) Build JWS payload: include iat, response kid, digestAlgorithm, and digest of httpBody String digestB64 = sha256Base64(httpBody); String jwsPayload = new org.json.JSONObject() .put("iat", java.time.Instant.now().getEpochSecond()) .put("v-c-api-response-kid", kidFromCert(mat.responseCert.cert))  // instruct server to encrypt to your API Response MLE key .put("digestAlgorithm", "SHA-256") .put("digest", digestB64) .toString(); // 5) Sign the JWS with the REST – Certificate private key String signedJws = signAsJws(jwsPayload, mat.signingCert); // 6) Send the HTTP request // POST /your/api // Content-Type: application/json // Authorization: Bearer <signedJws> /* Body: { "encryptedRequest": "<encryptedJwe>" } */ // 7) Handle the response (decrypt if needed with API Response MLE private key) String apiResponse = /* http call result as string */; org.json.JSONObject resp = new org.json.JSONObject(apiResponse); String finalPayload = resp.has("encryptedResponse") ? decryptJwe(resp.getString("encryptedResponse"), mat.responseCert) : apiResponse;