Decrypt payment credentials

For security reasons, token and cardholder data included in the returned paymentCredential object is encrypted using JSON Web Encryption (JWE).

Before forwarding the payment data to your payment gateway for processing, use the following steps to decrypt the JWE payload using your private key.

Step 1: Convert your private key to DER format

Convert your PEM-formatted private key to DER format so that it can be correctly loaded by the decryption logic.

For example:

$ openssl pkcs8 -topk8 -in <your PEM private key> -outform DER -nocrypt -out rsapriv.der

Step 2: Decrypt the JWE-encrypted data

Decrypt the encrypted 3DS.data structure (containing the token details) and the encryptedMessage field (containing the cardholder's billing information and username).

The following code snippets are example decryption implementations in Java and C#, and an example of a decrypted 3DS.data structure:

  • Example decryption implementation in Java:

    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyFactory;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.util.Base64;
    import javax.crypto.Cipher;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    public class DeveloperPortalSample {
    
        public static void main(String[] args) throws Exception {
            // Example JWE string (replace with your actual JWE and private key path)
            String encryptedText = {{encryptedPayload}};
            String privateKeyPath = "./rsapriv.der";
    
            String PRIVATE_KEY = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get(privateKeyPath)));
            String result = decryptJWE(encryptedText, PRIVATE_KEY);
            System.out.println(result); 
        }
    
        public static String decryptJWE(String encryptedText, String privateKeyText) throws Exception {
            // Split JWE parts by '.'
            String delims = "[.]";
    
            String[] tokens = encryptedText.split(delims);
    
            if (tokens.length < 5) {
                throw new IllegalArgumentException("Invalid JWE format");
            }
    
            // Decode and parse JWE header
            byte[] headerBytes = Base64.getUrlDecoder().decode(tokens[0]);
            String headerJson = new String(headerBytes);
            ObjectMapper mapper = new ObjectMapper();
            JsonNode header = mapper.readTree(headerJson);
    
            // Extract algorithm information from header
            String alg = header.has("alg") ? header.get("alg").asText() : "RSA1_5";
            String enc = header.has("enc") ? header.get("enc").asText() : "A128GCM";
    
            // Convert private key
            byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyText);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);
    
            // Decode encrypted key, IV, ciphertext, and authentication tag
            byte[] encKey = Base64.getUrlDecoder().decode(tokens[1]);
            byte[] iv = Base64.getUrlDecoder().decode(tokens[2]);
            byte[] cipherText = Base64.getUrlDecoder().decode(tokens[3]);
            byte[] tag = Base64.getUrlDecoder().decode(tokens[4]);
    
            // Create Cipher instance based on key management algorithm
            String keyManagementAlgorithm;
            boolean useAAD = false;
    
            if ("RSA-OAEP".equals(alg)) {
                keyManagementAlgorithm = "RSA/ECB/OAEPPadding";
                // At Samsung, OAEP uses AAD (Additional Authenticated Data)
                useAAD = true;
            } else if ("RSA1_5".equals(alg)) {
                keyManagementAlgorithm = "RSA/ECB/PKCS1Padding";
                // While RSA1_5 does not use AAD
                useAAD = false;
            } else {
                throw new IllegalArgumentException("Unsupported key management algorithm: " + alg);
            }
    
            // Decrypt the CEK (Content Encryption Key)
            Cipher decryptCipher = Cipher.getInstance(keyManagementAlgorithm);
            decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] plainEncKey = decryptCipher.doFinal(encKey);
    
            // Create Cipher instance based on content encryption algorithm
            String contentEncryptionAlgorithm;
            int gcmTagLength;
    
            if ("A128GCM".equals(enc) || "A256GCM".equals(enc)) {
                contentEncryptionAlgorithm = "AES/GCM/NoPadding";
                gcmTagLength = 128;
            } else {
                throw new IllegalArgumentException("Unsupported content encryption algorithm: " + enc);
            }
    
            // Decrypt the content
            Cipher contentCipher = Cipher.getInstance(contentEncryptionAlgorithm);
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(gcmTagLength, iv);
            SecretKeySpec keySpec = new SecretKeySpec(plainEncKey, "AES");
            contentCipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
    
            // AAD handling: Use Base64Url-encoded header bytes as AAD 
            if (useAAD) {
                byte[] encodedHeader = Base64.getUrlEncoder().withoutPadding().encode(headerBytes);
                contentCipher.updateAAD(encodedHeader);
            }
    
            // Concatenate cipherText and tag, then pass to doFinal
            byte[] cipherData = new byte[cipherText.length + tag.length];
            System.arraycopy(cipherText, 0, cipherData, 0, cipherText.length);
            System.arraycopy(tag, 0, cipherData, cipherText.length, tag.length);
            byte[] plainText = contentCipher.doFinal(cipherData);
    
            return new String(plainText, java.nio.charset.StandardCharsets.UTF_8);
        }
    
  • Example decryption implementation in C#:

    using System;
    using System.IO;
    using System.Text;
    using System.Text.Json.Nodes;
    using System.Security.Cryptography;
    
    public static void Main(string[] args)
    {
        // Example JWE string (replace with your actual JWE and private key path)
        string encryptedText = {{encryptedPayload}};
        string privateKeyPath = "./rsapriv.der";
    
        // Read the private key file (DER format)
        byte[] privateKeyBytes = File.ReadAllBytes(privateKeyPath);
    
        // Decrypt the JWE
        string result = decryptJWE(encryptedText, privateKeyBytes);
    
        // Print the result
        Console.WriteLine(result);
    }
    
    public static string decryptJWE(string encryptedText, byte[] privateKeyBytes)
    {
        // Split JWE parts by '.'
        var parts = encryptedText.Split('.');
        if (parts.Length < 5)
          throw new ArgumentException("Invalid JWE format");
    
        // Decode and parse JWE header
       var headerBytes = Base64UrlDecode(parts[0]);
        var headerJson = Encoding.UTF8.GetString(headerBytes);
        var header = JsonNode.Parse(headerJson);
    
        // Extract algorithm information from header
        string alg = header?["alg"]?.ToString() ?? "RSA1_5";
        string enc = header?["enc"]?.ToString() ?? "A128GCM";
    
        // Convert private key (assume PKCS8 DER)
        using var rsa = RSA.Create();
        rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
    
        // Decode encrypted key, IV, ciphertext, and authentication tag
        var encKey = Base64UrlDecode(parts[1]);
        var iv = Base64UrlDecode(parts[2]);
        var cipherText = Base64UrlDecode(parts[3]);
        var tag = Base64UrlDecode(parts[4]);
    
        // Create Cipher instance based on key management algorithm
        bool useAAD = false;
        if (alg == "RSA-OAEP")
        {
            // At Samsung, OAEP uses AAD (Additional Authenticated Data)
            useAAD = true;
        }
        else if (alg == "RSA1_5")
        {
            // While RSA1_5 does not use AAD
            useAAD = false;
        }
        else
        {
            throw new ArgumentException($"Unsupported key management algorithm: {alg}");
        }
    
        // Decrypt the CEK (Content Encryption Key)
        byte[] plainEncKey = alg == "RSA-OAEP"
            ? rsa.Decrypt(encKey, RSAEncryptionPadding.OaepSHA1)
            : rsa.Decrypt(encKey, RSAEncryptionPadding.Pkcs1);
    
        // Decrypt the content
        using var aes = new AesGcm(plainEncKey, 16);
        var plainText = new byte[cipherText.Length];
        if (useAAD)
        {
            // AAD handling: Use Base64Url-encoded header bytes as AAD 
            var encodedHeader = Encoding.ASCII.GetBytes(Base64UrlEncode(headerBytes));
            aes.Decrypt(iv, cipherText, tag, plainText, encodedHeader);
        }
        else
        {
            aes.Decrypt(iv, cipherText, tag, plainText);
        }
        return Encoding.UTF8.GetString(plainText).TrimEnd('\0');
    }
    
    private static byte[] Base64UrlDecode(string input)
    {
        string s = input.Replace('-', '+').Replace('_', '/');
        switch (s.Length % 4)
        {
            case 2: s += "=="; break;
            case 3: s += "="; break;
        }
        return Convert.FromBase64String(s);
    }
    
    private static string Base64UrlEncode(byte[] input)
    {
        return Convert.ToBase64String(input)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }
    
  • Example of decrypted 3DS.data output:

    {
        "amount": "100",
        "currency_code": "USD",
        "utc": "1719388643614",
        "eci_indicator": "5",
        "tokenPAN": "5185731679991253",
        "tokenPanExpiration": "0127",
        "cryptogram": "AKkeaVcvwHfmAMmud6r3AoACFA=="
    }