How to decrypt data with your data decryption key (DDK)

When signing up to finAPI, you receive not only a client_id and client_secret for your application, but also a data decryption key. This key must be used in certain scenarios where finAPI will give your client access to user-related data outside of any user context (i.e. without the user being authorized). In order to avoid that a stranger can access user-related data just with the use of your client credentials in those scenarios, finAPI will perform an additional encryption of this data (i.e. in addition to the usual HTTPS communication). Your client application will have to decrypt this data using your data decryption key and the algorithm described below. Please keep your data decryption key at some place safe, and seperated from your client credentials.

The scenarios where you have to use your data decryption key are currently the following:

- When you want to change a user's password as a client (i.e. without the user being authorized). Here, the service "Request password change" will return a token that you will need to decrypt and then pass to the "Execute password change" service.

- When you receive push notifications from finAPI that contain detailed user-specific data. For more information, please read the documentation about push notifications, and especially take note of the "details" field of the various notifications, which will contain the encrypted data that you'll have to decrypt.

The algorithm that finAPI uses to encrypt data is the AES-128 algorithm with CBC (Cipher Block Chaining) and PKCS#5 padding. This algorithm uses a Secret Key, that is generated using PBKDF2 (Password-Based Key Derivation Function; see https://en.wikipedia.org/wiki/PBKDF2), where:

  • Pseudo Random Function (PRF) is Hmac-SHA1 (HmacSHA1)
  • Password is your application's data encryption key
  • Salt is also your application's data encryption key, but converted as a hexadecimal number to a byte array
  • iterations count (c) is 1000
  • key size (dkLen) is 128

In (mock) code, the encryption logic of finAPI looks like this:

encrypted text = 
    convert a byte array to a base64 string (
        encrypt with AES-128 (
            Secret Key, described above,
            initial vector, that is your application's data decryption key, converted as a hexadecimal number to a byte array,
            plain text that must be encrypted, converted to byte array using UTF-8 encoding
        )
    )

Thus, to decrypt the encrypted data, your application must implement the following decryption logic:

plain text = 
    convert a byte array to a string using UTF-8 encoding (
        decrypt with AES-128 (
            Secret Key, described above
            initial vector, that is your application's data decryption key, converted as a hexadecimal number to a byte array,
            encrypted text that must be decrypted, converted as a base64 string to a byte array
        )
    )

Here is an example that you can check your implementation against to see if it is correct:

  • Encrypted text: VzQvibakzi5ednt5RMMQYRJXVgL31NWk3E/sSi7PFbpIupcuW3Cgd2KWTxvqLNPt
  • Data Decryption Key:
    8deec885781c421794ceda8af70a5e63
  • Decrypted text:
    45b057dd-a036-491f-ab1c-fbb9a4c3dcb9

 

In case you want to use JavaScript for the decryption, you can use this sample implementation:

var AesUtil = function (keySize, iterationCount) {
    this.keySize = keySize / 32;
    this.iterationCount = iterationCount;
};

AesUtil.prototype.generateKey = function (dataDecryptionKey) {
    var salt = dataDecryptionKey;
    return CryptoJS.PBKDF2(
        dataDecryptionKey,
        CryptoJS.enc.Hex.parse(salt),
        {
            keySize: this.keySize,
            iterations: this.iterationCount
        });
};

AesUtil.prototype.encrypt = function (dataDecryptionKey, plainText) {
    var key = this.generateKey(dataDecryptionKey);
    var initialVector = dataDecryptionKey;
    var encrypted = CryptoJS.AES.encrypt(
        plainText,
        key,
        {
            iv: CryptoJS.enc.Hex.parse(initialVector)
        });
    return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
};

AesUtil.prototype.decrypt = function (dataDecryptionKey, cipherText) {
    var key = this.generateKey(dataDecryptionKey);
    var cipherParams = CryptoJS.lib.CipherParams.create({
        ciphertext: CryptoJS.enc.Base64.parse(cipherText)
    });
    var initialVector = dataDecryptionKey;
    var decrypted = CryptoJS.AES.decrypt(
        cipherParams,
        key,
        {
            iv: CryptoJS.enc.Hex.parse(initialVector)
        });
    return decrypted.toString(CryptoJS.enc.Utf8);
};

var KEY_SIZE = 128,
    ITERATION_COUNT = 1000,
    aesUtil = new AesUtil(KEY_SIZE, ITERATION_COUNT);

$(function () {
    $('#encrypt').click(function () {
        var inputText = $('#inputText').val();
        var dataDecryptionKey = $('#dataDecryptionKey').val();
        var encryptedText = aesUtil.encrypt(dataDecryptionKey, inputText);
        $('#resultText').val(encryptedText);
    });

    $('#decrypt').click(function () {
        var inputText = $('#inputText').val();
        var dataDecryptionKey = $('#dataDecryptionKey').val();
        var decryptedText = aesUtil.decrypt(dataDecryptionKey, inputText);
        $('#resultText').val(decryptedText);
    });
})

When you want to use Java for decryption, you can use this class (you will need to have the apache commons (commons-codec) library to compile this class):

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.codec.binary.Base64.encodeBase64String;
import org.apache.commons.codec.binary.Hex;

/**
 * A classic implementation of the AES encryption algorithm. The same algorithm is implemented in JS
 * (utils/encryption-demo.js) to demonstrate it to customers.
 * The concept was taken from https://github.com/mpetersen/aes-example
 */
public class ClassicAesEncryptor {

    private static final int KEY_SIZE = 128;
    private static final int ITERATION_COUNT = 1000;
    private static final String ENCODING = "UTF-8";

    private final SecretKey key;
    private final Cipher cipher;
    private final String initialVector;

    /**
     * Create a new instance of the encryptor. Please note that the encryptionKey must contain hexadecimal symbols only!
     * 
     * @param encryptionKey
     *            encryption hexadecimal key
     */
    public ClassicAesEncryptor(String encryptionKey) {
        try {
            this.initialVector = encryptionKey;
            this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            this.key = generateKey(encryptionKey);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        }
    }

    public String encrypt(String plainText) {
        try {
            byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, initialVector, plainText.getBytes(ENCODING));
            return encodeBase64String(encrypted);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public String decrypt(String encryptedText) {
        try {
            byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, initialVector, decodeBase64(encryptedText));
            return new String(decrypted, ENCODING);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String generateEncryptionKey() {
        String uuid = UUID.randomUUID().toString();
        return uuid.toLowerCase().replaceAll("-", "");
    }

    private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
        try {
            cipher.init(encryptMode, key, new IvParameterSpec(Hex.decodeHex(iv.toCharArray())));
            return cipher.doFinal(bytes);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException | DecoderException
                | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException(e);
        }
    }

    private static SecretKey generateKey(String encryptionKey) {
        final String salt = encryptionKey; // just to note that we use the encryption key as a salt value
        try {
            KeySpec spec =
                    new PBEKeySpec(encryptionKey.toCharArray(), Hex.decodeHex(salt.toCharArray()), ITERATION_COUNT,
                            KEY_SIZE);
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            return new SecretKeySpec(secretKeyFactory.generateSecret(spec).getEncoded(), "AES");
        } catch (InvalidKeySpecException | NoSuchAlgorithmException | DecoderException e) {
            throw new IllegalStateException(e);
        }
    }
}

For PHP, you can use this snippet:

<?php
define('ENCRYPT_MODE', 'aes-128-cbc');
define('KEY_LENGTH', 16);
define('ITERATION_NUMBER', 1000);

function decrypt_message($message, $key) {
$encrypted_text = base64_decode($message);
$prepared_key = openssl_pbkdf2($key, hexToStr($key), KEY_LENGTH, ITERATION_NUMBER);
$iv = hexToStr($key);
return openssl_decrypt($encrypted_text, ENCRYPT_MODE, $prepared_key, OPENSSL_RAW_DATA, $iv);
}

function hexToStr($hex){
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2){
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
return $string;
}
?>

 

If you need an implementation for another language, please contact us.

Have more questions? Submit a request

0 Comments

Article is closed for comments.
Powered by Zendesk