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:
- The client fetches the public key(s) necessary to encrypt the sensitive properties
- The client encrypts the property string values and Base64 encodes the encrypted value
- 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.