Search code examples
javascriptencryptionopensslaescryptojs

CryptoJS AES decrypting a message encrypted in PHP openssl_encrypt


I have the following PHP code that uses openssl_encrypt() function to encrypt a message. Here is the code:

$ciphering = "AES-128-CTR";
$options = 0;
$encryption_iv = '5192001995060634';
$encryption_key = "TasKagitMakas";
  
function encrypt ($string) {
  global $ciphering, $options, $encryption_iv, $encryption_key;
 
  $encryption = openssl_encrypt($string, $ciphering, 
                $encryption_key, $options, $encryption_iv); 
  $encryption = strtr(base64_encode($encryption), '+/=', '-_,');
  return $encryption;
}

I was trying to reverse the process above and get the Javascript implementation using CryptoJS. Here's what I came up with:

var str = "bzB5UVNBclRHbWhlQUs4aHJoMHVxR1BJNEF1Sk9BRkpvbEpBRDFnVmg0MEx4RGtqWllvdUIrSW0vZGY3eG1KMVd2b2JxRFlOTnJ6N2FnPT0,";

str = str.split("-").join("+");
str = str.split("_").join("/");
str = str.split(",").join("=");

var encrypted = CryptoJS.enc.Base64.parse(str);
var encryptedStr = encrypted.toString(CryptoJS.enc.Utf8);
var key = "TasKagitMakas";
var iv  = "5192001995060634";

var decrypted = CryptoJS.AES.decrypt(encryptedStr, key, {iv: iv, mode: CryptoJS.mode.CTR});

console.log(decrypted.toString(CryptoJS.enc.Utf8));

This code gives me a blank output, just nothing. What am I doing wrong here? How can I correct my implementation?


Solution

  • PHP code:

    • openssl_encrypt pads the key with zero values until the specified key length is reached, i.e. the key TasKagitMakas is expanded to TasKagitMakas\0\0\0.
    • $options = 0 means that the ciphertext is implicitly Base64 encoded. Since the ciphertext is explicitly Base64 encoded again afterwards, it is Base64 encoded twice in total. This is unnecessary and should be changed, for example, with $options = OPENSSL_RAW_DATA.
    • For a stream cipher mode like CTR openssl_encrypt automatically disables the default PKCS7 padding.

    JavaScript code:

    • Since it was Base64 encoded twice in the PHP code, it is necessary to Base64 decode twice in the JavaScript code. This step is of course only necessary for the unchanged PHP code.
    • Key and IV must be parsed into a WordArray with the Utf8 Encoder. The extended key must be used.
    • CryptoJS.AES.decrypt expects the ciphertext as CipherParams object.
    • Unlike PHP, CryptoJS does not automatically disable the default PKCS7 padding for a stream cipher mode, i.e. it must be explicitly disabled.

    The following JavaScript code decrypts the ciphertext:

    var str = "bzB5UVNBclRHbWhlQUs4aHJoMHVxR1BJNEF1Sk9BRkpvbEpBRDFnVmg0MEx4RGtqWllvdUIrSW0vZGY3eG1KMVd2b2JxRFlOTnJ6N2FnPT0,";
    
    str = str.split("-").join("+");
    str = str.split("_").join("/");
    str = str.split(",").join("=");
    
    var encrypted = CryptoJS.enc.Base64.parse(str);           // Base64 decode twice (as long as this happens in the PHP code)
    var encrypted = encrypted.toString(CryptoJS.enc.Utf8);
    var encrypted = CryptoJS.enc.Base64.parse(encrypted);
    
    var key = CryptoJS.enc.Utf8.parse("TasKagitMakas\0\0\0"); // Expand the key and use the Utf8 encoder
    var iv  = CryptoJS.enc.Utf8.parse("5192001995060634");    // Use the Utf8 encoder
    
    var decrypted = CryptoJS.AES.decrypt(
      {
        ciphertext: encrypted                                 // Pass teh ciphertext as CipherParams object
      }, 
      key, 
      {
        iv: iv, 
        mode: CryptoJS.mode.CTR, 
        padding: CryptoJS.pad.NoPadding                       // Disable the PKCS7 padding
      });
    
    console.log(decrypted.toString(CryptoJS.enc.Utf8));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

    with the output:

    399002  Örnek2  Öğrenci student@ug.bilkent.edu.tr   Team1   6