Trustly API calls are secured by cryptographic signatures included on all request payloads and event notifications. Request signatures are required for production API requests and SDK operations. Additionally, Trustly strongly recommends that applications verify the signatures included in redirect notifications from Trustly UI SDKs and incoming webhook notifications.
Example Code
Check out Trustly's example Nestjs app for a working demonstration of how to generate and handle request signatures and integrate them with your frontend application.
Overview
This document provides comprehensive guidance on securing API requests to Trustly by generating and validating signatures to ensure data integrity and authenticity. This includes:
- Generating Request Signatures for production use of the Trustly UI and Trustly API
- Validating signatures on redirect functions from Trustly UI
- Validating signatures on event notifications
- Encrypting sensitive values before sending to Trustly
Generating Request Signatures
A request signature is essential for the Establish Data used by the Trustly UI and the POST /establish API endpoint to ensure the integrity of transactions, providing robust protection against potential tampering. The requestSignature
should be generated server-side to keep your accessKey
secure and out of client-side code. It is a Base64-encoded HMAC-SHA1 hash of the data in the request payload, created using your accessKey
.
Steps to Generate a Signature
- Concatenate Parameters
- Gather all the request parameters, including the
accessId
- Concatenate these parameters into a single string. Ensure the parameters are in the order referenced below.
- Gather all the request parameters, including the
- Hash the Concatenated String
- Use the HMAC-SHA1 hashing algorithm.
- Use your
accessKey
as the key for the HMAC algorithm. - Base64 encode the resulting hash.
- Attach the Signature to Your Request
- Include the generated signature in the
requestSignature
property of the payload.
- Include the generated signature in the
BETA Hashing Algorithm Options
If desired, an alternative hashing algorithm may be used to generate the requestSignature. Trustly will assume by default that HMAC-SHA1 has been used to generate the hash so if an alternative is used, the algorithm name and a colon should be prepended to the resulting signature after it is Base64 encoded. For example:
requestSignature: "HmacSHA512:RuYv5esOLn2f4F4NU5bz7YGLITEtLVQrciiEm0dCrn/O1DJ9E5hLwIYTyd5DHBJBxAhdxuKp655bG/gymoPt+g=="
Supported algorithms include:
- HMAC-SHA1:
HmacSHA1
(Default value if no algorithm prefix is included) - HMAC-SHA512:
HmacSHA512
Parameter Ordering
In order for the signature to be validated correctly, the order of parameters in the hash must be strictly maintained. Any field in the list below which is present in the request payload must be included in the generated requestSignature
and must appear in the order below.
Note on null values
Do not include any fields below that are not present in the request payload. Additionally, do not include fields with
null
values unless they also appear in your request payload with anull
value.
- accessId
- merchantId
- description
- currency
- amount
- displayAmount
- minimumBalance
- merchantReference
- paymentType
- timeZone
- recurrence.startDate
- recurrence.endDate
- recurrence.frequency
- recurrence.frequencyUnit
- recurrence.frequencyUnitType
- recurrence.recurringAmount
- recurrence.automaticCapture
- verification.status
- verification.verifyCustomer
- customer.customerId
- customer.externalId
- customer.name
- customer.vip
- customer.taxId
- customer.driverLicense.number
- customer.driverLicense.state
- customer.address.address1
- customer.address.address2
- customer.address.city
- customer.address.state
- customer.address.zip
- customer.address.country
- customer.phone
- customer.email
- customer.balance
- customer.currency
- customer.enrollDate
- customer.externalTier
- customer.externalTierTrustScore
- customer.dateOfBirth
- account.nameOnAccount
- account.name
- account.type
- account.profile
- account.accountNumber
- account.routingNumber
- beneficiary.name
- beneficiary.taxId
- beneficiary.address.address1
- beneficiary.address.city
- beneficiary.address.state
- beneficiary.address.zip
- beneficiary.address.country
- beneficiary.dateOfBirth
- beneficiaryAccount.iban
- beneficiaryAccount.paymentProvider.name
- beneficiaryAccount.paymentProvider.routingNumber
- beneficiaryAccount.paymentProvider.swift
- beneficiaryAccount.paymentProvider.country
- transactionId
- onlinePPSubtype
- customer.customData.payins.volume30Days
- customer.customData.payins.volume90Days
- customer.customData.payins.volume365Days
- customer.customData.payouts.volume30Days
- customer.customData.payouts.volume90Days
- customer.customData.payouts.volume365Days
Examples
For a functional example in the context of a backend application, checkout our NestJS example app. Alternatively, three examples are found below:
- Readable but verbose Javascript example
- Optimized ES6 Javascript example
- Java example
const Crypto = require('crypto');
const generateSignature = (establishData, accessKey) => {
let query = '';
query += `accessId=${establishData.accessId}`;
query += `&merchantId=${establishData.merchantId}`;
query += `&description=${establishData.description}`;
query += `¤cy=${establishData.currency}`;
query += `&amount=${establishData.amount}`;
if (establishData.displayAmount) query += `&displayAmount=${establishData.displayAmount}`;
if (establishData.minimumBalance) query += `&minimumBalance=${establishData.minimumBalance}`;
query += `&merchantReference=${establishData.merchantReference}`;
query += `&paymentType=${establishData.paymentType}`;
if (establishData.timeZone) query += `&timeZone=${establishData.timeZone}`;
if (establishData.paymentType === 'Recurring' && establishData.recurrence) {
if (establishData.recurrence.startDate) query += `&recurrence.startDate=${establishData.recurrence.startDate}`;
if (establishData.recurrence.endDate) query += `&recurrence.endDate=${establishData.recurrence.endDate}`;
if (establishData.recurrence.frequency) query += `&recurrence.frequency=${establishData.recurrence.frequency}`;
if (establishData.recurrence.frequencyUnit) query += `&recurrence.frequencyUnit=${establishData.recurrence.frequencyUnit}`;
if (establishData.recurrence.frequencyUnitType) query += `&recurrence.frequencyUnitType=${establishData.recurrence.frequencyUnitType}`;
if (establishData.recurrence.recurringAmount) query += `&recurrence.recurringAmount=${establishData.recurrence.recurringAmount}`;
if (establishData.recurrence.automaticCapture) query += `&recurrence.automaticCapture=${establishData.recurrence.automaticCapture}`;
}
if (establishData.verification) {
if (establishData.verification.status) query += `&verification.status=${establishData.verification.status}`;
if (establishData.verification.verifyCustomer) query += `&verification.verifyCustomer=${establishData.verification.verifyCustomer}`;
}
if (establishData.customer) {
if (establishData.customer.customerId) query += `&customer.customerId=${establishData.customer.customerId}`;
if (establishData.customer.externalId) query += `&customer.externalId=${establishData.customer.externalId}`;
if (establishData.customer.name) query += `&customer.name=${establishData.customer.name}`;
if (establishData.customer.vip !== undefined) query += `&customer.vip=${establishData.customer.vip}`;
if (establishData.customer.taxId) query += `&customer.taxId=${establishData.customer.taxId}`;
if (establishData.customer.driverLicense) {
if (establishData.customer.driverLicense.number) query += `&customer.driverLicense.number=${establishData.customer.driverLicense.number}`;
if (establishData.customer.driverLicense.state) query += `&customer.driverLicense.state=${establishData.customer.driverLicense.state}`;
}
if (establishData.customer.address) {
if (establishData.customer.address.address1) query += `&customer.address.address1=${establishData.customer.address.address1}`;
if (establishData.customer.address.address2) query += `&customer.address.address2=${establishData.customer.address.address2}`;
if (establishData.customer.address.city) query += `&customer.address.city=${establishData.customer.address.city}`;
if (establishData.customer.address.state) query += `&customer.address.state=${establishData.customer.address.state}`;
if (establishData.customer.address.zip) query += `&customer.address.zip=${establishData.customer.address.zip}`;
if (establishData.customer.address.country) query += `&customer.address.country=${establishData.customer.address.country}`;
}
if (establishData.customer.phone) query += `&customer.phone=${establishData.customer.phone}`;
if (establishData.customer.email) query += `&customer.email=${establishData.customer.email}`;
if (establishData.customer.balance) query += `&customer.balance=${establishData.customer.balance}`;
if (establishData.customer.currency) query += `&customer.currency=${establishData.customer.currency}`;
if (establishData.customer.enrollDate) query += `&customer.enrollDate=${establishData.customer.enrollDate}`;
if (establishData.customer.dateOfBirth) query += `&customer.dateOfBirth=${establishData.customer.dateOfBirth}`;
}
if (establishData.account) {
if (establishData.account.nameOnAccount) query += `&account.nameOnAccount=${establishData.account.nameOnAccount}`;
if (establishData.account.name) query += `&account.name=${establishData.account.name}`;
if (establishData.account.type) query += `&account.type=${establishData.account.type}`;
if (establishData.account.profile) query += `&account.profile=${establishData.account.profile}`;
if (establishData.account.accountNumber) query += `&account.accountNumber=${establishData.account.accountNumber}`;
if (establishData.account.routingNumber) query += `&account.routingNumber=${establishData.account.routingNumber}`;
}
// Required for International Money Transfer
if (establishData.beneficiary) {
if (establishData.beneficiary.name) query += `&beneficiary.name=${establishData.beneficiary.name}`;
if (establishData.beneficiary.taxId) query += `&beneficiary.taxId=${establishData.beneficiary.taxId}`;
if (establishData.beneficiary.address) {
if (establishData.beneficiary.address.address1) query += `&beneficiary.address.address1=${establishData.beneficiary.address.address1}`;
if (establishData.beneficiary.address.city) query += `&beneficiary.address.city=${establishData.beneficiary.address.city}`;
if (establishData.beneficiary.address.state) query += `&beneficiary.address.state=${establishData.beneficiary.address.state}`;
if (establishData.beneficiary.address.zip) query += `&beneficiary.address.zip=${establishData.beneficiary.address.zip}`;
if (establishData.beneficiary.address.country) query += `&beneficiary.address.country=${establishData.beneficiary.address.country}`;
}
if (establishData.beneficiary.dateOfBirth) query += `&beneficiary.dateOfBirth=${establishData.beneficiary.dateOfBirth}`;
}
if (establishData.beneficiaryAccount) {
if (establishData.beneficiaryAccount.paymentProvider) {
if (establishData.beneficiaryAccount.paymentProvider.name) query += `&beneficiaryAccount.paymentProvider.name=${establishData.beneficiaryAccount.paymentProvider.name}`;
if (establishData.beneficiaryAccount.paymentProvider.routingNumber) query += `&beneficiaryAccount.paymentProvider.routingNumber=${establishData.beneficiaryAccount.paymentProvider.routingNumber}`;
if (establishData.beneficiaryAccount.paymentProvider.swift) query += `&beneficiaryAccount.paymentProvider.swift=${establishData.beneficiaryAccount.paymentProvider.swift}`;
if (establishData.beneficiaryAccount.paymentProvider.country) query += `&beneficiaryAccount.paymentProvider.country=${establishData.beneficiaryAccount.paymentProvider.country}`;
}
if (establishData.beneficiaryAccount.iban) query += `&beneficiaryAccount.iban=${establishData.beneficiaryAccount.iban}`;
}
if (establishData.transactionId) query += `&transactionId=${establishData.transactionId}`;
const requestSignature = Crypto.createHmac('sha1', accessKey).update(query).digest('base64');
return requestSignature;
}
const crypto = require('crypto');
// Ordered and final keys list
const signatureKeysOrdered = ['accessId', 'merchantId', 'description', 'currency', 'amount', 'displayAmount', 'minimumBalance', 'merchantReference', 'paymentType', 'timeZone', 'recurrence.startDate', 'recurrence.endDate', 'recurrence.frequency', 'recurrence.frequencyUnit', 'recurrence.frequencyUnitType', 'recurrence.recurringAmount', 'recurrence.automaticCapture', 'verification.status', 'verification.verifyCustomer', 'customer.customerId', 'customer.externalId', 'customer.name', 'customer.vip', 'customer.taxId', 'customer.driverLicense.number', 'customer.driverLicense.state', 'customer.address.address1', 'customer.address.address2', 'customer.address.city', 'customer.address.state', 'customer.address.zip', 'customer.address.country', 'customer.phone', 'customer.email', 'customer.balance', 'customer.currency', 'customer.enrollDate', 'customer.externalTier', 'customer.dateOfBirth', 'account.nameOnAccount', 'account.name', 'account.type', 'account.profile', 'account.accountNumber', 'account.routingNumber', 'transactionId'];
export const generateSignature = (establishData, accessKey) => {
const searchParams = [];
// Use only parameters present on the key list
for (const key of signatureKeysOrdered) {
// If the key doesn't have a child, push it now
if (!key.includes('.')) {
establishData[key] && searchParams.push(`${key}=${establishData[key].toString()}`);
continue;
}
// If the key has a child, split it into sub-keys
const subKeys = key.split('.');
let data = establishData;
// For each sub-key, set its value
for (const subKey of subKeys) {
const innerValue = data[subKey];
if (typeof innerValue === 'object') {
// If the sub-key has a sub-key, process it again
data = innerValue;
} else {
// If not, push it
innerValue && searchParams.push(`${key}=${innerValue.toString()}`);
break;
}
}
}
// Concatenate all parameters into a plain string
const query = searchParams.join('&');
// Encode the string
const requestSignature = crypto.createHmac('sha1', accessKey).update(query).digest('base64');
return requestSignature;
};
public class TrustlySignature {
public static class Account { //Used only on account verification
public String nameOnAccount;
public String name;
public String type;
public String profile;
public String accountNumber;
public String routingNumber;
}
public static class Recurrence { //Used only when payment type is Recurring
public Date startDate;
public Date endDate;
public Integer frequency;
public Integer frequencyUnit;
public Integer frequencyUnitType;
public BigDecimal recurringAmount;
public Boolean automaticCapture;
}
public static class Address {
public String address1;
public String address2;
public String city;
public String state;
public String zip;
public String country;
}
public static class DriverLicense {
public String number;
public String state;
}
public static class Customer {
public String customerId; //Trustly's customer ID
public String externalId; //Merchant’s customer ID
public String name;
public String vip;
public String taxId;
public DriverLicense dL;
public Address address;
public String phone;
public String email;
public String balance;
public String currency;
public String enrollDate;
public String dateOfBirth;
}
public static class Verification {
public String status;
public Boolean verifyCustomer;
}
public String createRequestSignature(String accessId,
String accessKey,
String merchantId,
String description,
String currency,
BigDecimal amount,
BigDecimal displayAmount,
BigDecimal minimumBalance,
String merchantReference,
String paymentType,
String timeZone,
Recurrence r,
Verification v,
Customer c,
Account a,
String transactionId) throws Exception {
String str = "accessId="+ accessId;
str += "&merchantId="+merchantId;
str += "&description="+description;
str += "¤cy="+currency;
str += "&amount="+amount.setScale(2); //2 decimal places such as 13.25
if(displayAmount != null) str += "&displayAmount="+displayAmount.setScale(2); //2 decimal places such as 13.25
if(minimumBalance != null) str += "&minimumBalance="+minimumBalance.setScale(2); //2 decimal places such as 13.25
str += "&merchantReference="+merchantReference;
str += "&paymentType="+paymentType;
if(timeZone != null) str += "&timeZone="+timeZone;
if("Recurring".equals(paymentType) && r != null){
if(r.startDate != null) str += "&recurrence.startDate=" + r.startDate.getTime();
if(r.endDate != null) str += "&recurrence.endDate=" + r.endDate.getTime();
if(r.frequency != null) str += "&recurrence.frequency="+r.frequency;
if(r.frequencyUnit != null) str += "&recurrence.frequencyUnit="+r.frequencyUnit;
if(r.frequencyUnitType != null) str +="&recurrence.frequencyUnitType="+r.frequencyUnitType;
if(r.recurringAmount != null) str +="&recurrence.recurringAmount="+r.recurringAmount;
if(r.automaticCapture != null) str +="&recurrence.automaticCapture="+r.automaticCapture;
}
if (v != null) {
if(v.status != null) str +="&verification.status=" + v.status;
if(v.verifyCustomer != null) str +="&verification.verifyCustomer=" + v.verifyCustomer;
}
if(c != null) {
if(c.customerId != null) str +="&customer.customerId=" + c.customerId;
if(c.externalId != null) str +="&customer.externalId=" + c.externalId;
if(c.name != null) str +="&customer.name=" + c.name;
if(c.vip != null) str +="&customer.vip=" + c.vip;
if(c.taxId != null) str +="&customer.taxId=" + c.taxId;
if(c.dL != null) {
if(c.dL.number != null) str +="&customer.driverLicense.number=" + c.dL.number;
if(c.dL.state != null) str +="&customer.driverLicense.state=" + c.dL.state;
}
if(c.address != null) {
if(c.address.address1 != null) str +="&customer.address.address1=" + c.address.address1;
if(c.address.address2 != null) str +="&customer.address.address2=" + c.address.address2;
if(c.address.city != null) str +="&customer.address.city=" + c.address.city;
if(c.address.state != null) str +="&customer.address.state=" + c.address.state;
if(c.address.zip != null) str +="&customer.address.zip=" + c.address.zip;
if(c.address.country != null) str +="&customer.address.country=" + c.address.country;
}
if(c.phone != null) str +="&customer.phone=" + c.phone;
if(c.email != null) str +="&customer.email=" + c.email;
if(c.balance != null) str +="&customer.balance=" + c.balance.setScale(2);
if(c.currency != null) str +="&customer.currency=" + c.currency;
if(c.enrollDate != null) str +="&customer.enrollDate=" + c.enrollDate;
if(c.dateOfBirth != null) str +="&customer.dateOfBirth=" + c.dateOfBirth;
}
if(a != null) {
if(a.nameOnAccount != null) str +="account.nameOnAccount=" + a.nameOnAccount;
if(a.name != null) str +="account.name=" + a.name;
if(a.type != null) str +="account.type=" + a.type;
if(a.accountNumber!= null) str +="account.accountNumber=" + a.accountNumber;
if(a.routingNumber!= null) str +="account.routingNumber=" + a.routingNumber;
}
if(transactionId != null) str += "&transactionId="+transactionId;
return TrustlySignature.doHashing(str,accessKey);
}
public static String doHashing(String str, String accessKey) throws Exception {
Mac = Mac.getInstance("HmacSHA1");
SecretKeySpec signingKey = new SecretKeySpec(accessKey.getBytes(),"HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(str.getBytes());
return encodeBase64(rawHmac);
}
public static String encodeBase64(byte[] binary){
return DatatypeConverter.printBase64Binary(binary);
}
}
Validate the Redirect Signature
When handling a redirect notification from the Trustly UI, you can verify the notification's authenticity by calculating the requestSignature and comparing it to the value included in the notification payload.
To calculate a requestSignature
, follow these steps:
Given the following returnUrl
:
https://merchant.com/Trustly/return?transactionId=1002655801&transactionType=1&merchantReference=123123&status=2&payment.paymentType=4&payment.paymentProvider.type=1&payment.account.verified=false&panel=1&requestSignature=2uvoRTIEFWzfsbZwXokudh5P5rs%3D&instantPayoutAvail=true
- Remove the
requestSignature
parameter- Strip the
requestSignature
parameter (including&
) and it's value from the string. - Given the above example, the result would be:
https://merchant.com/Trustly/return?transactionId=1002655801&transactionType=1&merchantReference=123123&status=2&payment.paymentType=4&payment.paymentProvider.type=1&payment.account.verified=false&panel=1
- Strip the
- Calculate the Signature
- Using your
accessKey
, generate a Base64-encoded HMAC-SHA1 (or alternative) hash of the remaining query parameters.Alternative Algorithms Beta
If your app has been configured to receive redirect signatures using an alternative algorithm, the requestSignature included in the redirect will be prefixed with the algorithm label. For example:
requestSignature: "HmacSHA512:RuYv5esOLn2f4F4NU5bz7YGLITEtLVQrciiEm0dCrn/O1DJ9E5hLwIYTyd5DHBJBxAhdxuKp655bG/gymoPt+g=="
Confirm app-level configurations with your Trustly account manager.
- Given our example, the result would be:
2uvoRTIEFWzfsbZwXokudh5P5rs=
- Using your
- Compare the Signatures
- Compare the calculated hash (
2uvoRTIEFWzfsbZwXokudh5P5rs=
) to the decoded hash that was passed in the query string (2uvoRTIEFWzfsbZwXokudh5P5rs=
).
- Compare the calculated hash (
If the signatures match, the notification is authentic.
Check API Version
For API versions below 1.18.0, the
requestSignature
included in the payload sent to the providedreturnUrl
endpoint is calculated using ONLY the query parameters. Remove the base url and paths from the string above before proceeding to step 2.For API versions below 1.170.0 the
requestSignature
included in the payload sent to the providedcancelUrl
endpoint is calculated using ONLY the query parameters. Remove the base url and paths from the string above before proceeding to step 2.
Validate the Notification Signature
The request signature is calculated as a HMAC-SHA1 of the request parameters using accessKey
as the signing key. To calculate, follow these steps:
1. Read the entire POST
request body.
merchantId=1002463580&merchantReference=cb180040-7210-4ab9-97b7-415824754802&paymentType=2&transactionType=3&eventId=1002593570&eventType=Authorize&objectId=1002593555&objectType=Transaction&message=&timeZone=Etc%2FUTC&createdAt=1556234040954&accessId=M8RaHgEjBE54zuFYMRQq&paymentProviderTransaction.status=AC100&paymentProviderTransaction.statusMessage=AC100&status=2&statusMessage=Authorized
2. Decode the string using UTF-8
.
merchantId=1002463580&merchantReference=cb180040-7210-4ab9-97b7-415824754802&paymentType=2&transactionType=3&eventId=1002593570&eventType=Authorize&objectId=1002593555&objectType=Transaction&message=&timeZone=Etc/UTC&createdAt=1556234040954&accessId=M8RaHgEjBE54zuFYMRQq&paymentProviderTransaction.status=AC100&paymentProviderTransaction.statusMessage=AC100&status=2&statusMessage=Authorized
3. Using your accessKey
, calculate the signature and generate a Base64 encoded HMAC-SHA1 (or alternative) hash.
EYN3GXasrVU1vQ1uyYz22NNQdy4=
4. Grab the Authorization
header and remove the Basic
prefix.
TThSYUhnRWpCRTU0enVGWU1SUXE6RVlOM0dYYXNyVlUxdlExdXlZejIyTk5RZHk0PQ==
5. Decode the Base64 string to get a accessId:signature
string.
M8RaHgEjBE54zuFYMRQq:EYN3GXasrVU1vQ1uyYz22NNQdy4=
Alternative Algorithms Beta
If your app has been configured to receive redirect signatures using an alternative algorithm, the Authorization header will take the following form:
Authorization= Basic {accessId}:HmacSHA512:{requestSignature}
6. Split the decoded string on the :
to the get the signature.
EYN3GXasrVU1vQ1uyYz22NNQdy4=
- Compare the values calculated in Steps 3 (
EYN3GXasrVU1vQ1uyYz22NNQdy4=
) and 6 (EYN3GXasrVU1vQ1uyYz22NNQdy4=
). If they match, the request is valid. If they do not match and the failures continue for a long period of time, contact Trustly.
You can use the example Header and Request above, with an accessKey
of vMBWAvMXdPM27F9qZEkr
to confirm your signature verification code is working properly.
Example code for validating the notification signature
const decodeURI = (encodedString) => {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#decoding_query_parameters_from_a_url
// decodeURIComponent cannot be used directly to parse query parameters from a URL. It needs a bit of preparation
return decodeURIComponent(encodedString.replace(/\+/g, ' '));
}
const isValidSignature = (request, header, apiKey) => {
const test = Crypto.createHmac('sha1', apiKey).update(decodeURI(request)).digest('base64');
const auth = Buffer.from(header, 'base64').toString('utf8').split(':');
return test === auth[1];
}
Encrypt a field value
It is also possible to encrypt specific attributes sent on the establish data object to Trustly. The encryption is done using the AES-256-CBC algorithm and your Trustly accessKey
as the encryption key. The encrypted attributes start with the prefix crypt2:
and the full encrypted value (including the prefix) must be used when calculating the request signature.
Example code snippet:
const Crypto = require('crypto');
const accessKey = 'YOUR_ACCESS_KEY';
const attributeValue = '123123456'; //can be any PII or other sensitive data
const algorithm = 'aes-256-cbc';
const encoding = 'base64';
const iv = Buffer.from(Crypto.randomBytes(16)).toString('hex').slice(0, 16);;
const keyHash = Crypto.createHash('sha256').update(accessKey).digest();
const cipher = Crypto.createCipheriv(algorithm, keyHash.slice(0, 32), iv);
cipher.setAutoPadding(true);
let encrypted = cipher.update(iv + attributeValue, 'utf8', encoding);
encrypted += cipher.final(encoding);
console.log('response', 'crypt2:' + encrypted);
public class EncryptedAttribute {
public static final String AES_ECB_PKCS_5_PADDING = "AES/ECB/PKCS5Padding";
private static volatile SecureRandom numberGenerator = new SecureRandom();
public static byte[] salt(byte[] data) {
byte[] salt = new byte[4];
numberGenerator.nextBytes(salt);
byte[] salted = new byte[data.length + salt.length];
System.arraycopy(salt, 0, salted, 0, salt.length);
System.arraycopy(data, 0, salted, salt.length, data.length);
return salted;
}
public static byte[] unsalt(byte[] data) {
byte[] unsalted = new byte[data.length - 4];
System.arraycopy(data, 4, unsalted, 0, unsalted.length);
return unsalted;
}
public static String encodeBase64(byte[] binary) {
return Base64.getEncoder().encodeToString(binary);
}
public static byte[] decodeBase64(String str) {
try {
return Base64.getDecoder().decode(str);
} catch (Exception e) {
return new byte[0];
}
}
public static String encryptAttribute(String attribute, String accessKey) throws Exception {
if (attribute == null || attribute.length() == 0) {
return attribute;
}
Cipher cipher = Cipher.getInstance(AES_ECB_PKCS_5_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, buildKey(accessKey));
return "crypt:" + encodeBase64(cipher.doFinal(salt(attribute.getBytes("UTF-8"))));
}
public static String decryptAttribute(String attribute, String accessKey) {
if (attribute == null || accessKey == null || !attribute.startsWith("crypt2:")) { //If attribute is not
encrypted
return it
return attribute;
}
try {
Cipher cipher = Cipher.getInstance(AES_ECB_PKCS_5_PADDING);
cipher.init(Cipher.DECRYPT_MODE, buildKey(accessKey));
return new String(unsalt(cipher.doFinal(decodeBase64(attribute.substring("crypt2:".length())))), "UTF-
8 ");
}
catch (Exception e) {
return attribute;
}
}
private static SecretKeySpec buildKey(String accessKey) throws Exception {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
digester.update(accessKey.getBytes("UTF-8"));
byte[] key = Arrays.copyOf(digester.digest(), 32);
SecretKeySpec spec = new SecretKeySpec(key, "AES");
return spec;
}
}
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class Program
{
public static void Main()
{
const string accessKey = "YOUR_ACCESS_KEY";
const string attributeValue = "123123456";
var random = new Byte[16];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(random);
var iv = BitConverter.ToString(random).Replace("-", "").Substring(0, 16);
var keyHash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(accessKey));
Aes aes = Aes.Create();
aes.Key = keyHash;
aes.IV = UTF8Encoding.UTF8.GetBytes(iv);
aes.Mode = CipherMode.CBC;
ICryptoTransform cipher = aes.CreateEncryptor(aes.Key, aes.IV);
var inputBuffer = aes.IV.Concat(UTF8Encoding.UTF8.GetBytes(attributeValue)).ToArray();
var resultArray = cipher.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
var encrypted = Convert.ToBase64String(resultArray, 0, resultArray.Length);
Console.WriteLine("crypt2:" + encrypted);
}
}
1. Create an initialization vector (iv
) containing 16 random characters.
2. Create a SHA256 hash of your accessKey
.
3. Using the first 32 characters of your accessKey hash (from step 2) and the initialization vector (from step 1), create a cipher.
4. Using the cipher key (from step 3), the initialization vector (from step 1), and the attribute value to encrypt, update the cipher.
5. Create an encrypted string by finalizing the cipher using base64
encoding.
6. Concatenate crypt2:
and your encrypted string, and pass the value in the establishData
value that is passed to the SDK:
customer: {
name: 'John Smith',
taxId: 'crypt2:uFVg4qGHj7ZtwSv1tkFAL7pBJ5x8zsehYgNdU51w5yA=',
address: {
country: 'US',
}
},