Skip to main content

Tokenizing Encrypted Card Data

In some cases, there may be a desire to encrypt card data prior to sending it to Braintree via the GraphQL API. Please note that this approach does not completely remove the PCI Compliance requirement.

Here are the steps to encrypt credit/debit card values and tokenize the payment method:

  1. Retrieve the client-side encryption key from the Control Panel
  2. Using the client-side encryption key, encrypt the values of the card on your client or server
  3. Tokenize the encrypted values using the tokenizeCreditCard mutation to generate a payment method ID
  4. Use the payment method ID to vault or transact on the the payment method

Retrieve a Client-Side Encryption Key

  • Log into the Braintree Control Panel
  • Click the Settings wheel icon in the top right corner
  • Click API in the drop-down menu.
  • Copy the "CSE Key" from the "Client-Side Encryption Keys" section

Similar to API Keys, CSE key permissions are scoped to the permission of the user obtaining the key from the Control Panel.

Use Client-Side Encryption Key to Encrypt Fields

The following fields support being encrypted.

  • number
  • expirationMonth
  • expirationYear
  • cvv
  • cardholderName

The format for an encrypted field is

PREFIX + "$" + ENCRYPTED_KEY + "$" + CIPHER_TEXT + "\$" + SIGNATURE
  1. RAW_TEXT_TO_ENCRYPT: This is the string that you want to encrypt
  2. PREFIX: This is a string in the following format: $bt4|EXAMPLE_VALUE$. The second half of the PREFIX can contain any value up to 36 characters. We sugguest passing a value that provides significance or acts as an identifier
  3. ENCRYPTED_KEY: To begin, generate the RSA key pair, the AES key, and the HMAC key

Generating the RSA public key

  1. Convert your Client-Side Encryption public key into a byte array
  2. ASN1 decode the byte array. The resulting two parts will be your modulus and exponent, respectively
  3. Create a new RSA public key using the modulus and exponent

Java Example

private PublicKey generateRsaKey(String cseKey) throws Exception {
byte[] convertedCseKey = Base64.getDecoder().decode(CSE_KEY);
ASN1InputStream input = new ASN1InputStream(convertedCseKey);
ASN1Primitive obj = input.readObject();
RSAPublicKey key = RSAPublicKey.getInstance(obj);
RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}

Generate the AES and HMAC keys

  1. Generate an AES key that will be used later when encrypting with AES
  2. Generate an HMAC key that will be used later to sign the message
  3. Get the concatenated byte array of the previously generated AES key and the HMAC key, which will be a byte array referred to now as combinedKey
  4. Base64 encode the combinedKey
  5. RSA encrypt the encodedKey which results in the hexEncryptedKey
  6. Base64-encode the hexEncryptedKey

The output of the Base64 is called the ENCRYPTED_KEY.

Java Example

private SecretKey generateAesKey() throws NoSuchAlgorithmException {
KeyGenerator aesGenerator = KeyGenerator.getInstance("AES");
aesGenerator.init(256);
return aesGenerator.generateKey();
}
private String createEncryptedKey(PublicKey rsaKey, byte[] encodedKey) throws Exception {
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, rsaKey);
byte[] hexEncryptedKey = encryptCipher.doFinal(encodedKey);
return Base64.getEncoder().encodeToString(hexEncryptedKey);
}
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA256");
Key hmacKey = generator.generateKey();
SecretKeySpec hmacSecretKeySpec = new SecretKeySpec(hmacKey.getEncoded(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(hmacSecretKeySpec);
SecretKey aesKey = generateAesKey();
byte[] combinedKey = Bytes.concat(aesKey.getEncoded(), hmacSecretKeySpec.getEncoded());
byte[] encodedKey = Base64.getEncoder().encode(combinedKey);
String encryptedKey = createEncryptedKey(rsaKey, encodedKey);

Generate Cipher Text

Using the previously generated AES key, create the CIPHER_TEXT:

  1. Using Cipher Block Chaining mode, encrypt the byte array of the RAW_TEXT_TO_ENCRYPT using the generated AES key and an initialization vector (IV). This will be referred to as cipherTextBits
  2. Concatenate the IV and the cipherTextBits. This will be referred to as cipherTextAndIvBits
  3. Base64-encode concatenated IV and cipherTextBits

The output of the Base64 is called the CIPHER_TEXT.

Java Example

public byte[] generateCipherText(String field, SecretKey aesKey) throws Exception {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
byte[] cipherTextBits = cipher.doFinal(field.getBytes());
byte[] cipherTextAndIvBits = Bytes.concat(iv, cipherTextBits);
return Base64.getEncoder().encode(cipherTextAndIvBits);
}
byte[] byteCipherText = generateCipherText(field, aesKey);
String cipherText = new String(byteCipherText);

Generate Signature

Using the previously generated cipher text, create the SIGNATURE:

  1. SHA-256 HMAC sign the CIPHER_TEXT using the previously generated HMAC key. This is referred to as rawSignature
  2. Base64-encode rawSignature

The output of the Base64 is called the SIGNATURE

Java Example

byte[] encodedSignature = mac.doFinal(Base64.getDecoder().decode(byteCipherText));
String signature = Base64.getEncoder().encodeToString(encodedSignature);

Generate Encrypted Value

Finally, concatenate the pieces to obtain the encrypted string in this format:

PREFIX + "$" + ENCRYPTED_KEY + "$" + CIPHER_TEXT + "$" + SIGNATURE

Tokenize the encrypted values using the tokenizeCreditCard mutation

After generating values for all the fields you wish to encrypt, pass them into the TokenizeCreditCard mutation.

mutation

mutation ExampleTokenization($input: TokenizeCreditCardInput!) {
tokenizeCreditCard(input: $input) {
paymentMethod {
id
}
}
}

variables

{
"input": {
"creditCard": {
"number": "ENCRYPTED_VALUE",
"expirationMonth": "ENCRYPTED_VALUE",
"expirationYear": "ENCRYPTED_VALUE",
"cvv": "ENCRYPTED_VALUE"
}
}
}

Successful response

If the tokenization completed successfully, the result will contain a paymentMethodId. You can use this ID to vault or transact on the payment method.

response

{
"data": {
"tokenizeCreditCard": {
"paymentMethod": {
"id": "id_of_payment_method",
"usage": "SINGLE_USE",
"createdAt": "time_stamp"
}
}
}
}

Example Error Response

Example error response when invalid encrypted data is sent:

errors

{
"message": "Encrypted field is incorrectly formatted.",
"locations": [{"line": 2, "column": 3}],
"path": ["tokenizeCreditCard"],
"extensions": {
"errorType": "developer_error",
"errorClass": "VALIDATION",
"inputPath": ["input", "creditCard", "number"]
}
}

Example error response when an incorrect public key is used:

errors

{
"message": "Error decrypting encrypted value.",
"locations": [{"line": 2, "column": 3}],
"path": ["tokenizeCreditCard"],
"extensions": {
"errorType": "developer_error",
"errorClass": "VALIDATION",
"inputPath": ["input", "creditCard", "number"]
}
}

Next steps

After you receive the payment method ID, you can use it to create transactions, vault it, etc.

See also

Creating Transactions Guide