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 KeyAnchorIcon

  • 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 FieldsAnchorIcon

The following fields support being encrypted.

  • number
  • expirationMonth
  • expirationYear
  • cvv
  • cardholderName
The format for an encrypted field is
  1. Tab 1
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 keyAnchorIcon

  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
  1. Java
{
  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 keysAnchorIcon

  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.

  1. Java
{
  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 TextAnchorIcon

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.

  1. Java
{
  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 SignatureAnchorIcon

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

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

Generate Encrypted ValueAnchorIcon

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

  1. Tab 1
PREFIX + "$" + ENCRYPTED_KEY + "$" + CIPHER_TEXT + "$" + SIGNATURE

Tokenize the encrypted values using thetokenizeCreditCardmutationAnchorIcon

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

  1. Mutation
mutation ExampleTokenization($input: TokenizeCreditCardInput!) {
  tokenizeCreditCard(input: $input) {
    paymentMethod {
      id
    }
  }
}
  1. Variables
{
  "input": {
    "creditCard": {
      "number": "ENCRYPTED_VALUE",
      "expirationMonth": "ENCRYPTED_VALUE",
      "expirationYear": "ENCRYPTED_VALUE",
      "cvv": "ENCRYPTED_VALUE"
    }
  }
}

Successful responseAnchorIcon

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

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

Example Error ResponseAnchorIcon

Example error response when invalid encrypted data is sent:

  1. JSON
{
  "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:

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

Next stepsAnchorIcon

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

See alsoAnchorIcon

Creating Transactions Guide