FILTER BY TAG

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

PAN Encryption Key Locations
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.
The card network is determined based on the bank identification number (BIN) of the PAN:
  • Visa: PAN starts with
    4
    .
  • Mastercard: PAN starrds with
    51
    through
    55
    , or
    2221
    through
    2720
    .
  • American Express: PAN starts with
    34
    or
    37
    .

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']);