如何通过 Crypto-JS 解密 AES 128-CBC?

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

我有一个 PHP 代码可以用密钥解密有效负载,我正在尝试使用 crypto-js 库在 JavaScript 中编写完全相同的代码,但我得到错误的结果。

有效载荷中的前 16 个字节 - 是向量,其余部分 - 是有用的信息。

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);

JS 代码无效

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>

请帮我改进这个 JS 代码

JavaScript代码中存在几个问题:

  • 密钥不能作为字符串传递,而是作为 WordArray 传递(否则 CryptoJS 使用密钥派生函数)。
  • IV和密文判断不正确
  • 密文必须作为 CipherParams 对象(或 Base64 编码字符串)传递。
  • 填充必须是 PKCS7(解密也适用于 NoPadding,但不会删除填充字节)。
  • 明文必须是utf8解码(.toString()默认十六进制编码)

有关详细信息,请参阅 CryptoJS 文档,尤其是章节 The Cipher Input and The Cipher Output

以下JavaScript代码解密密文:

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,'\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>