On This Page
Client-Side PAN Encryption Keys
During the guest checkout flow, the customer enters their card details such as the
PAN, expiration date, CVV, and billing address directly into the merchant UI. This
sensitive data must be encrypted before it is sent to
Click to Pay
for card enrollment and checkout. You can use PAN encryption keys to encrypt the
sensitive data.Every card network provides an RSA public key for encrypting card data. These RSA
public keys are included in the decoded JWT response from the
/uctp/v1/sessions
request. Your client-side code must extract
the correct key based on the card number that is entered by the customer and then
use that key to generate a JSON Web Encryption (JWE) compact serialization
string.The encrypted card data is passed to the
checkout()
method as
encryptedCard
in the SDK. The SDK then forwards the encrypted
card data to Click to Pay
, where it is decrypted using the
corresponding key. This means that the unencrypted PAN does not pass through the
payment network or pass through your server.IMPORTANT
You can store RSA public keys, however,
Cybersource
recommends that you use the keys from the URL.
This ensures that you use the most current key.Key in Capture Context JWT
Card Network | JWT Path | Key Format |
|---|---|---|
Visa | payload.ctx[0].data.paymentConfigurations.SRCVISA.panEncryptionKey | RSA JWK |
Mastercard | payload.ctx[0].data.paymentConfigurations.SRCMASTERCARD.panEncryptionKey | |
American Express | payload.ctx[0].data.paymentConfigurations.SRCAMEX.panEncryptionKey
|
IMPORTANT
Keys are located in the
paymentConfigurationa
field object within the
data
object. You must ensure the correct path to the key
within the field object. If you do not do this and the path is incorrect, the
key will be empty.- Visa: PAN starts with4.
- Mastercard: PAN starrds with51through55, or2221through2720.
- American Express: PAN starts with34or37.
Encryption Algorithm
The card data is encrypted using a two-layer scheme per the JWE standard (RFC
7516):
- RSA-OAEP-256: A random 256-bit AES content encryption key (CEK) is generated and encrypted (wrapped) using the network's RSA public key.
- A256GCM: The card data JSON is encrypted using the CEK with AES-256-GCM, which provides both confidentiality and integrity.
The result is a JWE Compact Serialization string with five Base64URL-encoded parts
separated by dots:
header.encryptedKey.iv.ciphertext.authTag
. The
kid
from the PAN encryption key JWK is included in the JWE
header. This enables the SRC receiving system to know which private key to use for
decryption.JavaScript Example: Extract PAN Encryption Keys from Capture Context
After you verify the capture context JWT on your server, extract the PAN encryption
keys from the payload and pass them to the client alongside the decoded JWT. The
keys are located within the
paymentConfigurations
field object
for each card network.// Server-side: extract PAN encryption keys from the verified JWT payload function extractPanEncryptionKeys(jwtPayload) { const networks = ['SRCVISA', 'SRCMASTERCARD', 'SRCAMEX']; const panEncryptionKeys = {}; for (const entry of (jwtPayload.ctx || [])) { for (const network of networks) { const key = entry?.data?.paymentConfigurations?.[network]?.panEncryptionKey; if (key) { panEncryptionKeys[network] = key; } } } return panEncryptionKeys; } // After verifying the capture context JWT: const decoded = await verifyJwt(captureContextJwt); const panEncryptionKeys = extractPanEncryptionKeys(decoded.payload); // Return both the decoded JWT and the keys to the client res.json({ captureContext: captureContextJwt, decoded, panEncryptionKeys });
JavaScript Example: JWE Encrypt Card Data
These functions run in the browser using the Web Crypto API. The
jweEncrypt
function takes a pre-stringified JSON payload and
the RSA JWK public key, and returns a JWE Compact Serialization string.// Base64url-encode a Uint8Array (no padding) function base64UrlEncode(data) { let binary = ''; for (const byte of data) { binary += String.fromCharCode(byte); } return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } // Base64url-encode a string (UTF-8) function base64UrlEncodeString(str) { return base64UrlEncode(new TextEncoder().encode(str)); } // JWE-encrypt a plaintext string using an RSA-OAEP-256 public key (JWK) async function jweEncrypt(plaintext, jwk) { // Import the RSA public key for key wrapping const publicKey = await crypto.subtle.importKey( 'jwk', jwk, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['wrapKey'] ); // Generate a random 256-bit Content Encryption Key (CEK) const cek = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt'] ); // Wrap (encrypt) the CEK with the RSA public key const wrappedKey = new Uint8Array( await crypto.subtle.wrapKey('raw', cek, publicKey, { name: 'RSA-OAEP' }) ); // Generate a random 96-bit IV for AES-GCM const iv = crypto.getRandomValues(new Uint8Array(12)); // Build the JWE protected header (include kid if present on the JWK) const header = { alg: 'RSA-OAEP-256', enc: 'A256GCM' }; if (jwk.kid) { header.kid = jwk.kid; } const encodedHeader = base64UrlEncodeString(JSON.stringify(header)); // The Additional Authenticated Data (AAD) is the ASCII bytes of the encoded header const aad = new TextEncoder().encode(encodedHeader); // Encrypt the plaintext with AES-256-GCM using the CEK const plaintextBytes = new TextEncoder().encode(plaintext); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv, additionalData: aad, tagLength: 128 }, cek, plaintextBytes ); // AES-GCM appends the 16-byte auth tag to the ciphertext const encryptedArray = new Uint8Array(encrypted); const ciphertext = encryptedArray.slice(0, encryptedArray.length - 16); const authTag = encryptedArray.slice(encryptedArray.length - 16); // Build JWE Compact Serialization: header.encryptedKey.iv.ciphertext.tag return [ encodedHeader, base64UrlEncode(wrappedKey), base64UrlEncode(iv), base64UrlEncode(ciphertext), base64UrlEncode(authTag), ].join('.'); } // Usage: encrypt card data with the appropriate network key const cardPayload = { card: { primaryAccountNumber: 'XXXXXXXXXXX, /* ... */ } }; const encryptedCard = await jweEncrypt(JSON.stringify(cardPayload), panEncryptionKeys['SRCVISA']);