Search code examples
javascriptencryptionaessalesforce-marketing-cloudampscript

crypto-js AES exchange with Marketing Cloud (AMPScript)


I'm trying to use crypto-js to manage encryption and decryption of data from Marketing Cloud (AMPScript). I checked the lib docs and several "guides" on the web. The lib docs don't talk about using the IV, while other guides are very confusing. I initially tried to work with a simple flow of encrypt-decrypt, but I could't seem to get it right. The decrypted value was always empty.

Now thanks to Topaco, I changed the key to be 12 characters long and now I can encrypt/decrypt.

I also had to adjust the decrypt part a bit, passing a cypher object.

I'll need to check with them how exactly they're using the parameters and if they're doing multiple encryptions in order to work with their sample data. I'll update the question.

Here is my code.

const key = enc.Utf8.parse('Harold%Finch');
const iv = enc.Utf8.parse('11111111111111111111111111111111');
const secret = enc.Utf8.parse('[email protected]');
const encrypted = AES.encrypt(secret, key, {
  iv: iv,
  mode: mode.CBC,
  padding: pad.Pkcs7,
});

const encryptedString = encrypted.toString();
// "2VxmNeqlTolVpgHrk+tdEO3+xS8XOjClikUjB+zAGis="

// Convert the base64-encoded string to a CipherParams object
const ciphertext = enc.Base64.parse(encryptedString);
const cipherParams = lib.CipherParams.create({ ciphertext });

const decrypted = AES.decrypt(cipherParams, key, {
  iv: iv,
  mode: mode.CBC,
  padding: pad.Pkcs7,
});

// Convert the decrypted data to a string (UTF-8)
const decryptedString = decrypted.toString(enc.Utf8);

console.info('decrypted', decryptedString);
// "[email protected]"

On their end the code looks like:

set @encAES = EncryptSymmetric(@str, "aes", @null, @password, @null, @salt, @null, @initVector)

But I'm still not getting the same result. I'm checking this documentation to understand how it works precisely in terms of defaults (the padding for example)

https://ampscript.guide/encryptsymmetric/

Blitz here


Solution

  • Ok it appears that under the hood the EncryptSymmetric method in AMPScript creates a key from the password and salt, using the PBKDF2 key derivation (as stated by @Topaco).

    So we need to do the same. I'm using crypto-js because I have a set of encode-decode functions shared between frontend and edge middleware, but if you're at backend side you can use the native crypto implementation of Node.

    The original AMPScript code:

      %%[ 
        set @str = [EmailAddress]
        set @password="xyQ8n55tKi$@@4dy"
        set @initVector='198e3f0733ec4791'
        set @salt = "c2dc41eeab1e513a"  
    
        set @encAES = EncryptSymmetric(@str, "aes", @null, @password, @null, @salt, @null, @initVector)
        set @decAES = DecryptSymmetric(@encAES, "aes", @null, @password, @null, @salt, @null, @initVector)
      ]%%
    
      import { PBKDF2, AES, enc, mode, pad, algo } from 'crypto-js';
    
      const str = '[email protected]';
      const password = 'xyQ8n55tKi$@@4dy';
      const initVector = '198e3f0733ec4791';
      const salt = 'c2dc41eeab1e513a';
    
      // Encrypting the string using AES
      const encAES = encryptAES(str, password, salt, initVector);
      console.info('ENC AES', encAES);
    
      // Decrypting the encrypted string using AES
      const decAES = decryptAES(encAES, password, salt, initVector);
      console.info('DEC AES', decAES);
    
      function buildKey(password, salt) {
        return PBKDF2(password, enc.Hex.parse(salt), {
          keySize: 256 / 32,
          iterations: 1000,
          hasher: algo.SHA1,
        });
      }
    
      function encryptAES(data, password, salt, initVector) {
        return AES.encrypt(data, buildKey(password, salt), {
          iv: enc.Hex.parse(initVector),
          mode: mode.CBC,
          padding: pad.Pkcs7,
        }).toString();;
      }
    
      function decryptAES(data, password, salt, initVector) {
    
        return AES.decrypt(data, buildKey(password, salt), {
          iv: enc.Hex.parse(initVector),
          mode: mode.CBC,
          padding: pad.Pkcs7,
        }).toString(enc.Utf8);
      }
    

    Very wierdly on their side they said they can't use a 16 byte IV and we stuck with the 32 byte. I will check this in the future.