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:
- Retrieve the client-side encryption key from the Control Panel
- Using the client-side encryption key, encrypt the values of the card on your client or server
- Tokenize the encrypted values using the
tokenizeCreditCard
mutation to generate a payment method ID - 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
RAW_TEXT_TO_ENCRYPT
: This is the string that you want to encryptPREFIX
: This is a string in the following format:$bt4|EXAMPLE_VALUE$
. The second half of thePREFIX
can contain any value up to 36 characters. We sugguest passing a value that provides significance or acts as an identifierENCRYPTED_KEY
: To begin, generate the RSA key pair, the AES key, and the HMAC key
Generating the RSA public key
- Convert your Client-Side Encryption public key into a byte array
- ASN1 decode the byte array. The resulting two parts will be your
modulus
andexponent
, respectively - Create a new RSA public key using the
modulus
andexponent
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
- Generate an AES key that will be used later when encrypting with AES
- Generate an HMAC key that will be used later to sign the message
- 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
- Base64 encode the
combinedKey
- RSA encrypt the
encodedKey
which results in thehexEncryptedKey
- 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
:
- 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 ascipherTextBits
- Concatenate the IV and the
cipherTextBits
. This will be referred to ascipherTextAndIvBits
- 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
:
- SHA-256 HMAC sign the
CIPHER_TEXT
using the previously generated HMAC key. This is referred to asrawSignature
- 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.