Encryption

Some Apiture Digital Banking APIs pass sensitive data (such as passwords) or personally identifiable data (such as social security numbers). Such data is necessary for digital banking applications: regulations require financial institutions to verify the identity of their customers.

The APIs all operate using transport layer security (TLS or https) which encrypts the request and response data. The APIs provide an additional layer of encryption for this sensitive data in case a client becomes compromised and an attacker can intercept request bodies. if that happens, the sensitive data in the JSON requests is encrypted and secure from theft.

Let’s consider a hypothetical operation which passes a customer’s username and password in the request body. A the client should The request body contains both the user’s current username and password which are sensitive data. To prevent the user’s credentials from being visible outside of the API call, the operation requires the client to encrypt the values so that user credentials are not passed in clear text. This tutorial shows how a client can use the Apiture Digital Banking APIs encryption protocol to encrypt this sensitive data.

For this hypothetical scenario, the API operation uses a request body schema named sampleSensitiveRequestBody:

    sampleSensitiveRequestBody:
      title: Sample Sensitive Request Body
      description: A sample request body which contains secret data to be encrypyted.
        The properties `username` and `password` should be encrypted
        with the encryption key named `secret`.
      type: object
      required:
        - username
        - password
        - encryptedWith
      properties:
        username:
          description: >-
            A banking customer's login username.
          allOf:
            - $ref: '#/components/schemas/encryptedString'
          x-apiture-encrypt: secret
        password:
          description: >-
            A banking customer's login password.
          allOf:
            - $ref: '#/components/schemas/encryptedString'
          x-apiture-encrypt: secret
        encryptedWith:
          $ref: '#/components/schemas/encryptedWith'
      example:
        username: >-
          MDEyMzQ1NTQzMjE+PlRoaXMgc2FtcGxlIHZhbHVlIHNob3VsZCBiZSB0aGUgQmFzZTY0IGVuY29k
          ZWQgdmFsdWUgb2YgdGhlIGVuY3J5cHRlZCB1c2VybmFtZS48PDAxMjM0NTU0MzIxCjAxMjM0NTU0
          MzIxPj5UaGlzIHNhbXBsZWQgZGF0YSBpcyBub3QgYWN0dWFsbHkgZW5jcnlwdGVkLCBidXQgY29k
          ZWQgdG8gbG9vayBsaWtlIGJpbmFyeTw8MDEyMzQ1NTQzMjEKMDEyMzQ1NTQzMjE+PmRhdGEuPDww
          MTIzNDU1NDMyMQ==
        password: >-
          MDEyMzQ1NTQzMjE+PlRoaXMgc2FtcGxlIHZhbHVlIHNob3VsZCBiZSB0aGUgQmFzZTY0IGVuY29k
          ZWQgdmFsdWUgb2YgdGhlIGVuY3J5cHRlZCBwYXNzd29yZC48PDAxMjM0NTU0MzIxCjAxMjM0NTU0
          MzIxPj5UaGlzIHNhbXBsZWQgZGF0YSBpcyBub3QgYWN0dWFsbHkgZW5jcnlwdGVkLCBidXQgY29k
          ZWQgdG8gbG9vayBsaWtlIGJpbmFyeTw8MDEyMzQ1NTQzMjEKMDEyMzQ1NTQzMjE+PmRhdGEuPDww
          MTIzNDU1NDMyMQo=
        encryptedWith:
          username: secret-74ae2504D8E
          password: secret-74ae2504D8E

Let’s see how the client can create this request body. There are three steps:

  1. The client fetches the public key(s) necessary to encrypt the sensitive properties
  2. The client encrypts the property string values and Base64 encodes the encrypted value
  3. The client adds encryptedWith metadata to the request which describes how the properties are encrypted

Fetch Encryption Keys

The two properties, username and password, each have an x-apiture-encrypt annotation which define the encryption key the client uses to encrypt those properties – secret

The client should encrypt the current and new password using secret public encryption key obtained from the POST /publicEncryptionKeys.

POST https:///api.apiture.com/platform/publicEncryptionKeys
{
  names: ["secret"]
}

(See also the JavaScript example below.)

The encryptionKeys response contains a public key for each of the keys in the request body:

{
    "secret": {
      "name": "secret",
      "publicKey": "-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAl2/fCtf69EnMqw6O/6Wh9wFvKW80jjNfXEWbHh0cnWKW1i0Heg0B...\\n-----END RSA PUBLIC KEY-----",
      "id": "secret-48729783",
      "createdAt": "2020-10-09T05:01:16.375Z",
      "expiresAt": "2020-10-09T05:01:36.375Z"
    }
}

This response contains a public key (truncated here for brevity) corresponding to the name "secret". Each encryption key has an expiration; the service rotates keys frequently. The id value, secret-48729783 is the name of the current secret key rotation. We passes the id back to the service in the encryptedWith metadata that we add to the response below.

Encrypt Each Property

The client can now encrypt the two properties using that public key. Clients using JavaScript can use the crypto library. Note: The encrypted values must also be Base64 encoded to ensure correct transmission and decoding on the service side. The encrypt function performs both the encryption and Base64 encoding.

const crypto = require('crypto');

function encrypt(text, key) => {
  var buffer = Buffer.from(text);
  var encrypted = crypto.publicEncrypt(key, buffer);
  return encrypted.toString('base64');
};

const headers = {
  'Accept':'application/json',
  'Content-Type':'application/json',
  'Authorization': `Bearer ${currentClientCredentialsToken}`
};

const names = [ 'secret' ];

$.ajax({
  url: 'https://api.apiture.com/platform/publicEncryptionKeys',
  method: post',
  data: names,
  headers: headers,
  success: function(response) {
    // this is a rough outline...
    const username = .... // value from UI form ;
    const password = .... // value from UI form ;
    const secretPublicKey = response.keys.secret.publicKey;
    const secretId = response.keys.secret.id;
    const body = {
      username: encrypt(username, secretPublicKey);
      password: encrypt(password, secretPublicKey);
    };
    body.encryptedWith: {
      'username': secretId,
      'password': secretId
    };
    // pass the body to the API...
  },
  error: function(jqXHR jqXHR: jqXHR jqXHR, status: string, err: string) {
       // handle an error here
  }
})

The client creates a request body with the encrypted properties:

    const body = {
      username: encrypt(username, secretPublicKey);
      password: encrypt(password, secretPublicKey);
    };

Add encryptedWith Metadata to the Request

Finally, the client adds an encryptedWith object (encryptedWith) that contains metadata about the encrypted values. This is an object which maps the property name of each encrypted property in the JSON object to the id of the encryption key that the client used to encrypt that property. We used the same encryption key with the id "secret-48729783" to encrypt both username and password:

    body.encryptedWith = {
        'username': secretId,
        'password': secretId
      }
    };

In general, we must add an encryptedWith object to each object in the request that has encrypted data, including nested objects. For example, if the request had two objects a and b with encrypted properties x and y respectively:

{
  "a": {
    "x": "secret-value-1"
  },
  "b": {
    "y": "secret-value-1"
  }
}

we must add encryptedWith to both a and b after encrypting x and y. (That is, the encryptedWith object is always a direct sibling of each encrypted value.)

{
  "a" : {
     "x": "E34D2C1D05644410BB2B06A494D2BD3AC45607F21EA2EB435",
     "encryptedWith": {
       "x" : "secret-472939"
     }
  },
  "b" : {
     "y": "EA62EE8D14EC8BDF30C3FF4D77BA24AE69344C1E88B7250DA"
     "encryptedWith": {
       "y" : "secret-472939"
     }
  }
}

The client can then submit the updated request to the target API operation.

$.ajax({
  url: 'https://api.thirdparty.bank/auth/my/password',
  method: 'put',
  headers: headers,
  success: function(data) {
    ...
  },
  error: function(jqXHR jqXHR: jqXHR jqXHR, status: string, err: string) {
       // handle an error here
  }
})

Encryption Key Expiration and Rotation

The client can cache reuse the encryption keys. However, the keys expire after a period of time (several minutes, but not several hours). The getEncryptionKeys response includes the expiration time for each of the requested keys in the expiresAt property, a RFC 3339 date-time value in UTC time zone. The service automatically rotates the keys when they expire and returns a new id for the updated key after rotating the key. Thus, subsequent calls to getPublicEncryptionKeys return the same ids and expirations if the keys have not expired, or it may return new id values and expirations.