Encryption
Aside from the SSL encryption which is required for every request made to the XS2A API, the form data that the consumer provides is additionally encrypted. This encryption has to be implemented in the following way:
The payload is AES-256 encrypted using a randomly generated key (a 32 bytes hexadecimal string) and a randomly generated initialization vector (a 16 bytes hexadecimal string).
The plain text is padded using the "PKCS5" padding.
The AES mode is "CBC".
Then the hexadecimal string representation of the AES key is encrypted using the RSA public key and PKCS#1 v1.5 padding.
The latter is provided as a JSON Web Token in the property key
of the JSON object returned in the last call to the XS2A API endpoint, thus the call that also contained the form that is to be encrypted.
Beware that this key might change anytime and must therefore not be stored and reused.
The AES cipher text, the RSA encrypted AES key and the AES initialization vector are then sent to the XS2A API as the POST request body of the request to the XS2A API endpoint.
This request body consists of a JSON object that has three properties:
The cipher text (ct
), the initialization vector (iv
) and the encrypted key (ek
).
The aforementioned encryption process could also be described in pseudo-code as follows:
rsaPublicKey = decodeJwt(responseFromEndpoint.data.result.key);
aesKey = generateRandomAesKey(); // 32 bytes
aesKeyHexString = convertToHexString(aesKey);
aesInitializationVector = generateAesInitializationVector(); // 16 bytes
aesInitializationVectorHexString = convertToHexString(aesInitializationVector);
cipherText = AES.encrypt(plainTextPayload, aesKeyHexString, aesInitializationVectorHexString);
encryptedAesKey = RSA.encrypt(aesKeyHexString, rsaPublicKey, 'PKCS1');
requestPayload = '
{
"ct": cipherText,
"iv": aesInitializationVector,
"ek": encryptedAesKey
}
';
Example
A form has been filled out by the user and the entered information has to be transmitted to Open banking. by Klarna via the Auth API. In this example the actual payload that has to be encrypted looks like this:
{"form_identifier":"5aa6b523-bfed-40c6-8e16-d106bd860ac6","data":[{"key":"LOGINNAME__USER_ID","value":"johndoe"},{"key":"USER_PIN","value":"123456"}]}
This information has to be encrypted using the AES ecryption, thus an initialization vector and a key have to be randomly generated.
For this example the initialization vector and the key are going to be 4ad3c9a234ca5c3ea566d2ff4f71c748
(hexadecimal representation) and dc2de9e6afb989289fe427494c1adf0c48109867419ac919de63dd493eb24668
(base64 encoded), respectively.
The plain text is padded using the PKCS5 padding and then the encryption is applied which results in the following base64 encoded ciphertext:
AubzQQVfFsDFi+Ofm68cCzNFkrNbtP1fXanUmZHz0nzbLdUR0kYCY6Q+MGtQILqeK/SMlX/o/1KFdHSWSCl1c6k5g8xq147emOkwwMJUTFdovDkcTiq5Kcz3guO24DUwnV7r8kdmUUqJMuPhE27wsN+xJ8NgKhk/WJdim09nVBBShNQ2Skj13sq8xcDgRV1y5dGwJiqXaAeJ1a/RN7BNog==
What is left to do now is to encrypt the AES key using RSA with PKCS#1 v1.5 padding. The RSA public key is provided along with the form that is to be filled out. The property "key" contains a JSON Web Token that contains the following payload:
{
"modulus": "c1d94ab5b9aa65559cb44a55838bb90ddd5e713f54792deecd3e0841c9db122d2ffcf34184795977a759824c596a2e9ffecfab26f853be8dc17b52f15e8355ad108002e00719969fa2718445cecf888d40ee90c8f06b6902b06295b62b638d4a12f6fa842f4e50c7ba52f41ba045f7894fe7c9c966ca53b36a2a554bcfa466bfff5ccd2b6bbf5d2fdf14f6e91ca7e5de7597391b5ccf8c280ea82e13e6d46310e72c20021deb2e1562183eeba5f250007db0ac015c6f5b71efdfb4415708181f2616a974d5fd8435d3303bf27f469afeaf77889f215dd3729135aac7b86df6f13ccaea9eb4674271e57a80bcc51c5b29490e2a5a635ec0651157e1c2c29a41bd",
"exponent": "10001"
}
Using this information the RSA encrypted AES key can be obtained, hence the final payload of the post request that conveys the data to the Auth API resembles1 the following JSON string:
{
"ct": "AubzQQVfFsDFi+Ofm68cCzNFkrNbtP1fXanUmZHz0nzbLdUR0kYCY6Q+MGtQILqeK/SMlX/o/1KFdHSWSCl1c6k5g8xq147emOkwwMJUTFdovDkcTiq5Kcz3guO24DUwnV7r8kdmUUqJMuPhE27wsN+xJ8NgKhk/WJdim09nVBBShNQ2Skj13sq8xcDgRV1y5dGwJiqXaAeJ1a/RN7BNog==",
"iv": "4ad3c9a234ca5c3ea566d2ff4f71c748",
"ek": "Pect4rWxJrU4HREtImiKFwhC8tPSfbMU6cz+ZStoBVKamoF2vFb1jnSZPKp/h+8x3Q6Nt57odaBQWlOxwT2WCq6v2a1iTJuI7ESRw4fhw+h81i0c41HX1yuDKTgXTjJn3ucnifhQxxhJEel7A3o/hXbot/VokZ+qh6kV3hYGyjtZqQmxVbSiO1a/3nV+OJk2Rcti0yNJriPhJVEAk3/ObmbQ5pjJXVdu+ZWSMpmfuEmlTZhIw6I7mFALnQD9ausN9Sm67+nu9xwKgau+82uyjVo8A91IZIlB7zbTTPD8AkerhwBZgZb8B01/PZqx3UVJDg3v5xtehlHt0kQsseMf1A=="
}
iv
is a string containing hexadecimal data, ct
and ek
are base64 encoded.
1. The PKCS#1 v1.5 padding for RSA adds random bits to the plaintext resulting in different ciphertexts for identical plaintexts. Therefore it is practically impossible to obtain the value for ek
provided in this example. ↩