One of the easiest Javascript libraries for encryption I usually adopt is CryptoJS, quick setup and good support for most algorithms.
But I got an headache trying to make it talk with Salesforce, this was due to my relatively low encryption-topics training but also to a specific way Salesforce handles encryption.
I was surprised that none has ever had the same need before.
I’m not going to explain how I came up to this solution (one of the reasons is that I already forgot it…as I always say, my brain is a cool CPU but with a low amount of storage), but I’ll just give you the way I solved encrypted data exchange between a Javascript script (whether it is client or server side) and Salesforce.
In Apex encrypting and decrypting a string is quite easy:
//encrypt
String algorithmName = 'AES256';
Blob privateKey = Crypto.generateAesKey(256);
Blob clearText = Blob.valueOf('Encrypt this!');
Blob encr = Crypto.encryptWithManagedIV(algorithmName, privateKey, clearText);
system.debug('## ' + EncodingUtil.base64encode(encr));
//decrypt
Blob decr = Crypto.decryptWithManagedIV(algorithmName, privateKey, encr );
System.debug('## ' + decr.toString());
This could be an example of the output:
## Lg0eJXbDvxNfLcFMwJm6CkFtxy4pWgkmanTvKLcTttQ=
## Encrypt this!
For encryption noobs out there, the encrypted string changes every time you run the script.
The string if first encrypted with the AES256 algorithm and then decrypted using the same secret key (generated automatically by Salesforce).
All is done through Crypto class’ methods:
Valid values for algorithmName
are:– AES128
– AES192
– AES256These are all industry standard Advanced Encryption Standard (AES) algorithms with different size keys. They use cipher block chaining (CBC) and PKCS5 padding.
Salesforce HELP
PKCS5 padding is a subset of the more general PKCS7, that is supported by CryptJS, so it still works.
The only thing that is not clearly stated here (at least for my low storage brain) is that this method uses an Initialization Vector (IV, that is used together with the private key to generate the proper encryption iterations) which has a fixed 16 Bytes length.
Also, the IV is included within the encrypted string: this is the key point.
To encrypt and decrypt using the following method the CryptoJS must be aware of the first 16 Bytes of the IV and append it to (if we are encrypting from JS to Salesforce) or extract it from (if we are decrypting in JS from a Salesforce encrypted string) the encrypted string.
This is what I came up with after a bit of research (you have to deal with binary data when encrypting, that’s why we use Base64 to exchange keys and encrypted strings).
//from https://gist.github.com/darmie/e39373ee0a0f62715f3d2381bc1f0974
var base64ToArrayBuffer = function(base64) {
var binary_string = atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array( len );
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
};
//from //https://gist.github.com/72lions/4528834
var appendBuffer: function(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
};
//from //https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string
var arrayBufferToBase64 = function( arrayBuffer ) {
return btoa(
new Uint8Array(arrayBuffer)
.reduce(function(data, byte){
return data + String.fromCharCode(byte)
},
'')
);
},
//Encrypts the message with the given secret (Base64 encoded)
var encryptForSalesforce = function(msg, base64Secret){
var iv = CryptoJS.lib.WordArray.random(16);
var aes_options = {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: iv
};
var encryptionObj = CryptoJS.AES.encrypt(
msg,
CryptoJS.enc.Base64.parse(base64Secret),
aes_options);
//created a unique base64 string with "IV+EncryptedString"
var encryptedBuffer = base64ToArrayBuffer(encryptionObj.toString());
var ivBuffer = base64ToArrayBuffer((encryptionObj.iv.toString(CryptoJS.enc.Base64)));
var finalBuffer = appendBuffer(ivBuffer, encryptedBuffer);
return arrayBufferToBase64(finalBuffer);
};
//Decrypts the string with the given secret (both params are Base64 encoded)
var decryptFromSalesforce = function(encryptedBase64, base64Secret){
//gets the IV from the encrypted string
var arrayBuffer = base64ToArrayBuffer(encryptedBase64);
var iv = CryptoJS.enc.Base64.parse(arrayBufferToBase64(arrayBuffer.slice(0,16)));
var encryptedStr = arrayBufferToBase64(arrayBuffer.slice(16, arrayBuffer.byteLength));
var aes_options = {
iv: iv,
mode: CryptoJS.mode.CBC
};
var decryptObj = CryptoJS.AES.decrypt(
encryptedStr,
CryptoJS.enc.Base64.parse(base64Secret),
aes_options
);
return decryptObj.toString(CryptoJS.enc.Utf8);
};
By sharing the Base64 of the Salesforce generated secret (using the method Crypto.generateAesKey(256) ) between your JS client and Salesforce, you can store and exchange encrypted data with a blink of an eye.