Search code examples
javascriptencryptionaescryptojs

How to decrypt AES 128-CBC by Crypto-JS?


I have a PHP code that decrypts payload with a secret key, I'm trying to write exactly the same code in JavaScript using the crypto-js library, but I get the wrong result.

First 16 bytes in payload - is vector, remaining part - is useful information.

Working code in PHP - https://ideone.com/NJXkRK

function getPayload($app_secret_key, $data) {
  // Get the encryption key (16 first bytes of the app's client_secret key)
  $encryption_key = substr($app_secret_key, 0, 16);
 
  // Decrypt payload
  $json_data = aes_128_decrypt($encryption_key, $data);
 
  // Decode json
  $json_decoded = json_decode($json_data, true);
  return $json_data;
}
 
function aes_128_decrypt($key, $data) {
  // Ecwid sends data in url-safe base64. Convert the raw data to the original base64 first
  $base64_original = str_replace(array('-', '_'), array('+', '/'), $data);
 
  // Get binary data
  $decoded = base64_decode($base64_original);
 
  // Initialization vector is the first 16 bytes of the received data
  $iv = substr($decoded, 0, 16);
 
  // The payload itself is is the rest of the received data
  $payload = substr($decoded, 16);
 
  // Decrypt raw binary payload
  $json = openssl_decrypt($payload, "aes-128-cbc", $key, OPENSSL_RAW_DATA, $iv);
  //$json = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $payload, MCRYPT_MODE_CBC, $iv); // You can use this instead of openssl_decrupt, if mcrypt is enabled in your system
 
  return $json;
}
 
// Get payload from the GET and process it
$ecwid_payload = "ng7W9c9jLhkX7ATMpafNAd5Vt_skEaFAqnQaw0Ing1iwYQOwB0Q_CuCS8yQeHeorTdCpZWDTNrzhcq_umX7IaAFUPPgs0zyddY7Er1tA0aze5kWGHUV54fJHoVEJHMmVEi-G5g8ZnNopIFu0YQgQqLpCq8TP2zFJunSTA7VXHTmqHNAD2JXaUb-VylcJWzgV0vaCoGyHqaPbsNNw6HSWkAzhh8dLmsYB0uzsZ_zl3wVXubCL4p2N53PmNPBLCgoC";
$client_secret = "zcKf1Zt0UsO43S46Un3pxIgs91R1xMGs"; 
 
$result = getPayload($client_secret, $ecwid_payload);
 
print($result);

Not working JS code

function getPayload() {
  const payload =
    "ng7W9c9jLhkX7ATMpafNAd5Vt_skEaFAqnQaw0Ing1iwYQOwB0Q_CuCS8yQeHeorTdCpZWDTNrzhcq_umX7IaAFUPPgs0zyddY7Er1tA0aze5kWGHUV54fJHoVEJHMmVEi-G5g8ZnNopIFu0YQgQqLpCq8TP2zFJunSTA7VXHTmqHNAD2JXaUb-VylcJWzgV0vaCoGyHqaPbsNNw6HSWkAzhh8dLmsYB0uzsZ_zl3wVXubCL4p2N53PmNPBLCgoC";
  const key = "zcKf1Zt0UsO43S46Un3pxIgs91R1xMGs";
  // Get the encryption key (16 first bytes of the app's client_secret key)
  const encryption_key = key.substr(0, 16);
  

  // Decrypt payload
  const base64_original = payload.replace(/-/gi, "+").replace(/_/gi, "/");
  const data = aes_128_decrypt(encryption_key, base64_original);
  console.log(data);
}

function aes_128_decrypt(password, data) {
  const decoded = atob(data);
  let iv = decoded.substr(0, 16);
  let payload = decoded.substr(16);

  iv = CryptoJS.enc.Hex.parse(iv);

  const decrypted = CryptoJS.AES.decrypt(payload, password, {
    iv: iv,
    padding: CryptoJS.pad.NoPadding
  });
  return decrypted.toString();
}


window.onload = function() {
  getPayload();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

Please help me to improve this JS code


Solution

  • There are several issues in the JavaScript code:

    • The key must not be passed as a string, but as a WordArray (otherwise CryptoJS uses a key derivation function).
    • IV and ciphertext are not determined correctly.
    • The ciphertext must be passed as CipherParams object (or as Base64 encoded string).
    • The padding must be PKCS7 (decryption would also work with NoPadding, but the padding bytes wouldn't be removed).
    • The plaintext must be Utf8 decoded (.toString() hex encodes by default)

    For details see the CryptoJS documentation, especially the chapters The Cipher Input and The Cipher Output.

    The following JavaScript code decrypts the ciphertext:

    function getPayload() {
        const payload = "ng7W9c9jLhkX7ATMpafNAd5Vt_skEaFAqnQaw0Ing1iwYQOwB0Q_CuCS8yQeHeorTdCpZWDTNrzhcq_umX7IaAFUPPgs0zyddY7Er1tA0aze5kWGHUV54fJHoVEJHMmVEi-G5g8ZnNopIFu0YQgQqLpCq8TP2zFJunSTA7VXHTmqHNAD2JXaUb-VylcJWzgV0vaCoGyHqaPbsNNw6HSWkAzhh8dLmsYB0uzsZ_zl3wVXubCL4p2N53PmNPBLCgoC";
        const key = "zcKf1Zt0UsO43S46Un3pxIgs91R1xMGs";
        // Get the encryption key (16 first bytes of the app's client_secret key)
        //const encryption_key = key.substr(0, 16);
        const encryption_key = CryptoJS.enc.Utf8.parse(key.substr(0, 16));              // Parse the key into a WordArray
      
        // Decrypt payload
        const base64_original = payload.replace(/-/gi, "+").replace(/_/gi, "/");
        const data = aes_128_decrypt(encryption_key, base64_original);
        console.log(data.replace(/(.{56})/g,'$1\n'));                                   // {"store_id":20553036,"access_token":"secret_a9TmTJfRt3gyvxjJ9UwYjs9VQip3F7rp","public_token":"public_QQ99gUwVGdvKuZbLLyNZzDsvXF5iF3gh","view_mode":"PAGE","lang":"ru"}
        //console.log(JSON.parse(data));                                                // Convert JSON string into JavaScript object (optional)
    }
    
    function aes_128_decrypt(password, data) {
        /*
        const decoded = atob(data);
        let iv = decoded.substr(0, 16);
        let payload = decoded.substr(16);
        iv = CryptoJS.enc.Hex.parse(iv);  
        */
        var ivCiphertext = CryptoJS.enc.Base64.parse(data);                             // Parse data into a WordArray
        var iv = CryptoJS.lib.WordArray.create(ivCiphertext.words.slice(0, 16 / 4));    // Separate iv
        var payload = CryptoJS.lib.WordArray.create(ivCiphertext.words.slice(16 / 4));  //    and ciphertext
    
        //const decrypted = CryptoJS.AES.decrypt(payload, password, {               
        const decrypted = CryptoJS.AES.decrypt(
            {
                ciphertext: payload                                                     // Pass CipherParams object
            }, 
            password,                                                                   
            {
                iv: iv
                //padding: CryptoJS.pad.NoPadding                                       // Apply PKCS7 padding
            });
        //return decrypted.toString();
        return decrypted.toString(CryptoJS.enc.Utf8);                                  // Utf8 decode plaintext
    }
    
    window.onload = function() {
        getPayload();
    };
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>