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 the operation of changing the user’s password.
To change a the password for an already authenticated user, the client should
%[
POST a request to
/my/password](/open/apis/auth/index.html#op-changeUserPassword).
The request body contains both the user's current password and their new password,
but the
currentPassword and
newPassword` string properties in the request
body must be encrypted. This tutorial shows how a client can use the
Apiture Digital Banking APIs encryption protocol to encrypt this sensitive data.
The request body uses the
passwordChange
schema;
an excerpt if that schema is below.
passwordChange:
title: Password change
description: >-
Representation used to change a user password.
The request must contain the `currentPassword`, the `newPassword`.
The client encrypts these values using the
`secret` encryption key and store the corresponding encryption key aliases
in the `_encryption` metadata property.
allOf:
- $ref: 'https://production.api.apiture.com/schemas/common/abstractResource/v2.0.0/model.json'
- type: object
required:
- currentPassword
- newPassword
properties:
currentPassword:
description: >-
The user's current encrypted user password.
The client should prompt the user for their current password,
then encrypt it with the `secret` encryption key.
type: string
format: encrypted-password
x-apiture-encrypt: secret
newPassword:
description: >-
The user's encrypted new password.
The client should prompt the user for their new password,
then encrypt it with the `secret` encryption key.
type: string
format: encrypted-password
x-apiture-encrypt: secret
_encryption:
description: Metadata about the encrypted `currentPassword` and `newPassword` properties.
allOf:
- $ref: 'https://production.api.apiture.com/schemas/common/encryptionMetadata/v1.0.1/model.json'
example:
currentPassword: secret-48729783
newPassword: secret-48729783
example:
currentPassword: EDHJKE45785HURYUR89RID7LIULD8973HDODHWLIW474HQ4GP47H
newPassword: E5UFOUOI45IOFLISKUTYW4U6K34HKFJOE98YW4IYLLIWEO72PHH4
_encryption:
currentPassword: secret-48729783
newPassword: secret-48729783
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 encoded the encrypted value
- The client adds
_encryption
metadata to the request which describes how the properties are encrypted
Fetch Encryption Keys
The passwordChange
schema contains a x-apiture-encrypt
annotation that declares that the request body includes
properties encrypted using an encryption key named secret
.
This annotation lists all the encryption keys used in the request. In most cases,
only one encryption key is used for all properties that require encryption,
but it is possible that different properties require different encryption keys.
The two properties,
currentPassword
and newPassword
, also each have an x-apiture-encrypt
annotations
which define the encryption key the client uses to encrypt those properties.
The client should encrypt the current and new password using secret
public encryption key obtained from the GET /encryptionKeys
.
To obtain the secret
public keys, call the getEncryptionKeys
operation:
GET https:///api.thirdparty.bank/auth/encryptionKeys?keys=secret
(See also the JavaScript example below.)
The
encryptionKeys
response
contains a public key for each of the keys
in the request query parameter:
{
"_profile": "https://api.apiture.com/schemas/common/encryptionKeys/v1.0.0/profile.json",
"keys": {
"secret": {
"name": "secret",
"publicKey": "-----BEGIN RSA PUBLIC KEY-----\\nMIIBCgKCAQEAl2/fCtf69EnMqw6O/6Wh9wFvKW80jjNfXEWbHh0cnWKW1i0Heg0B...\\n-----END RSA PUBLIC KEY-----",
"alias": "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 alias
value, secret-48729783
is the name of the current secret
key rotation.
We passes the alias back to the service in the _encryption
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');
const encrypt = (text, key) => {
var buffer = Buffer.from(text);
var encrypted = crypto.publicEncrypt(key, buffer);
return encrypted.toString('base64');
};
const headers = {
'Accept':'application/hal+json',
'API-Key': myClientApiKey,
'Authorization': `Bearer ${currentAuthenticatedUserAccessToken}`
};
$.ajax({
url: 'https://api.thirdparty.bank/auth/encryptionKeys',
method: 'get',
data: '?keys=secret',
headers: headers,
success: function(response) {
// this is a rough outline, but not very robust
const currentPassword = .... // value from UI form ;
const newPassword = .... // value from UI form ;
const secretPublicKey = response.keys.secret.publicKey;
const secretAlias = response.keys.secret.alias;
const encrypted_currentPassword = encrypt(currentPassword, secretPublicKey);
const encrypted_newPassword = encrypt(newPassword, secretPublicKey);
// more code to be added here; see below...
},
error: function(jqXHR jqXHR: jqXHR jqXHR, status: string, err: string) {
// handle an error here
}
})
The client then creates a request body with the encrypted properties:
const request = {
currentPassword: encrypted_currentPassword,
newPassword: encrypted_newPassword
);
Add _encryption Metadata to the Request
Finally, the client adds an _encryption
object
(encryptionMetadata
) 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 alias of the encryption key that the client used to encrypt that property.
We used the same encryption key with the alias "secret-48729783"
to
encrypt both currentPassword
and newPassword
:
request._encryption = {
currentPassword: secretAlias, // has the value 'secret-48729783' in this example
newPassword: secretAlias, // also has the value 'secret-48729783' in this example
};
In general, we must add an _encryption
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 _encrypted
to both a
and b
after encrypting x
and y
.
(That is, the _encrypted
object is always a direct sibling of each encrypted value.)
{
"a" : {
"x": "E34D2C1D05644410BB2B06A494D2BD3AC45607F21EA2EB435",
"_encryption": {
"x" : "secret-472939"
}
},
"b" : {
"y": "EA62EE8D14EC8BDF30C3FF4D77BA24AE69344C1E88B7250DA"
"_encryption": {
"y" : "secret-472939"
}
}
}
The client can then submit the updated request
to the changeUserPassword
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
The client can reuse the encryption keys, but 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. The service automatically rotates the keys when they expire
and returns a new alias for the key after rotating the key.
Thus, subsequent calls to getEncryptionKeys
return the same aliases and expirations
if the key has not expired, or it may return new aliases and expirations.
Future SDK support
A future release of the Apiture Digital Banking APIs software development kit (SDK) will perform this encryption automatically. The client needs to only call the operation and pass in the raw data, and the SDK will fetch the encryption keys and encrypt the data with the public keys.