String encrypted using Crypto js can not decrypted via Crypto apex

apexcrypto

We are encrypting a string using Crypto js library.
And we want to decrypt that encrypted string using Crypto apex class. But unfortunately, that is not working.

JS Code

CryptoJS.AES.encrypt('123', '1234567887654321').toString();

Apex Code

Crypto.decryptWithManagedIV('AES128', Blob.valueOf('1234567887654321'), Blob.valueOf(encryptedStringViaCryptoJS));

This is giving below error in Salesforce when we execute above code in Developer Console

System.SecurityException: last block incomplete in decryption

Any idea what we are missing?

Best Answer

This article may help you: https://blog.enree.co/2019/05/salesforce-handle-encryption-and-decryption-with-apex-crypto-class-and-crypojs.html

Key point from SF docs:

The initialization vector is stored as the first 128 bits (16 bytes) of the encrypted Blob. Use either third-party applications or the decryptWithManagedIV method to decrypt blobs encrypted with this method.

Key point from the article:

To encrypt (or decrypt) using the following method the CryptoJS must be aware of the first 16 Bytes of the IV and append it to (or extract it from) the encrypted string.

Basically, here is the algorithm:

  • Prepare JS methods:
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);
};
  • Go to Developer Console and generate your base64Secret:
String key = '1234567887654321';
String base64Secret = EncodingUtil.base64Encode(Blob.valueOf('1234567887654321'));
System.debug(base64Secret); // MTIzNDU2Nzg4NzY1NDMyMQ==
  • Now, when you have base64Secret, you can run command:
var encrypted = encryptForSalesforce('123', 'MTIzNDU2Nzg4NzY1NDMyMQ==');
console.log(encrypted.toString()); // wNnUs8DUdyxB/1exoE23UaB+00Qt7vFBCvQCML0R2mQ= (will be different every time)
  • And in Apex it will be successfully decrypted:
String key = '1234567887654321';
Blob encryptedValue = EncodingUtil.base64Decode('wNnUs8DUdyxB/1exoE23UaB+00Qt7vFBCvQCML0R2mQ=');
Blob decrypted = Crypto.decryptWithManagedIV('AES128', Blob.valueOf(key), encryptedValue);
System.debug(decrypted.toString()); // 123
Related Topic