Encrypt with PHP, Decrypt with Javascript (cryptojs)

Solution 1:

I've required the same thing and i wrote a short library that works for CryptoJS 3.x and PHP with openssl support. Hope this helps, source plus example files here https://github.com/brainfoolong/cryptojs-aes-php

PHP Lib

/**
* Decrypt data from a CryptoJS json encoding string
*
* @param mixed $passphrase
* @param mixed $jsonString
* @return mixed
*/
function cryptoJsAesDecrypt($passphrase, $jsonString){
    $jsondata = json_decode($jsonString, true);
    $salt = hex2bin($jsondata["s"]);
    $ct = base64_decode($jsondata["ct"]);
    $iv  = hex2bin($jsondata["iv"]);
    $concatedPassphrase = $passphrase.$salt;
    $md5 = array();
    $md5[0] = md5($concatedPassphrase, true);
    $result = $md5[0];
    for ($i = 1; $i < 3; $i++) {
        $md5[$i] = md5($md5[$i - 1].$concatedPassphrase, true);
        $result .= $md5[$i];
    }
    $key = substr($result, 0, 32);
    $data = openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv);
    return json_decode($data, true);
}

/**
* Encrypt value to a cryptojs compatiable json encoding string
*
* @param mixed $passphrase
* @param mixed $value
* @return string
*/
function cryptoJsAesEncrypt($passphrase, $value){
    $salt = openssl_random_pseudo_bytes(8);
    $salted = '';
    $dx = '';
    while (strlen($salted) < 48) {
        $dx = md5($dx.$passphrase.$salt, true);
        $salted .= $dx;
    }
    $key = substr($salted, 0, 32);
    $iv  = substr($salted, 32,16);
    $encrypted_data = openssl_encrypt(json_encode($value), 'aes-256-cbc', $key, true, $iv);
    $data = array("ct" => base64_encode($encrypted_data), "iv" => bin2hex($iv), "s" => bin2hex($salt));
    return json_encode($data);
}

Javascript Lib

var CryptoJSAesJson = {
    stringify: function (cipherParams) {
        var j = {ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64)};
        if (cipherParams.iv) j.iv = cipherParams.iv.toString();
        if (cipherParams.salt) j.s = cipherParams.salt.toString();
        return JSON.stringify(j);
    },
    parse: function (jsonStr) {
        var j = JSON.parse(jsonStr);
        var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(j.ct)});
        if (j.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(j.iv)
        if (j.s) cipherParams.salt = CryptoJS.enc.Hex.parse(j.s)
        return cipherParams;
    }
}

Example Javascript

var encrypted = CryptoJS.AES.encrypt(JSON.stringify("value to encrypt"), "my passphrase", {format: CryptoJSAesJson}).toString();
var decrypted = JSON.parse(CryptoJS.AES.decrypt(encrypted, "my passphrase", {format: CryptoJSAesJson}).toString(CryptoJS.enc.Utf8));

Example PHP

$encrypted = cryptoJsAesEncrypt("my passphrase", "value to encrypt");
$decrypted = cryptoJsAesDecrypt("my passphrase", $encrypted);

Solution 2:

Security notice: The code on this answer is vulnerable to chosen-ciphertext attacks. See this answer instead for secure encryption.

Here is a working example of encrypting your string with PHP and decrypting it with CryptoJS.

On the PHP side:

Use MCRYPT_RIJNDAEL_128 (not 256) to pair with AES. The 128 here is the blocksize, not the keysize.

Send the IV, too. You need the IV to decrypt.

$text = "this is the text here";
$key = "encryptionkey";

// Note: MCRYPT_RIJNDAEL_128 is compatible with AES (all key sizes)
$iv = random_bytes(16);

$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);

echo "iv:".base64_encode($iv)."\n";
echo "ciphertext:".base64_encode($ciphertext)."\n";

Here is sample output from a test run:

iv:BMcOODpuQurUYGICmOqqbQ==
ciphertext:ZJAab8YtkRq5TL7uyIR7frM2b3krftJzn1pTqRTAda4=

IMPORTANT: Because we are not authenticating our ciphertext, decryption becomes vulnerable to padding oracle attacks. See also: authenticated encryption in PHP.

On the CryptoJS side:

Your key is only 13 ASCII printable characters which is very weak. Mcrypt padded the key to a valid keysize using ZERO bytes.

Convert the key and IV to word arrays.

I did not have much luck decrypting with ciphertext as a word array, so I've left it in Base64 format.

CryptoJS = require("crypto-js")

// Mcrypt pads a short key with zero bytes
key = CryptoJS.enc.Utf8.parse('encryptionkey\u0000\u0000\u0000')

iv = CryptoJS.enc.Base64.parse('BMcOODpuQurUYGICmOqqbQ==')

// Keep the ciphertext in Base64 form
ciphertext = 'ZJAab8YtkRq5TL7uyIR7frM2b3krftJzn1pTqRTAda4='

/**
 * DANGER DANGER WILL ROBINSON! <== Stop editing my answer or I will delete it.
 *
 * This example code doesn't demonstrate AUTHENTICATED ENCRYPTION
 * and is therefore vulnerable to chosen-ciphertext attacks.
 *
 * NEVER USE THIS CODE TO PROTECT SENSITIVE DATA!
 */

// Mcrypt uses ZERO padding
plaintext = CryptoJS.AES.decrypt(ciphertext, key, { iv: iv, padding: CryptoJS.pad.ZeroPadding })

// I ran this in nodejs
process.stdout.write(CryptoJS.enc.Utf8.stringify(plaintext))

Solution 3:

Don't go too dept with coding, just use base64 decoder

on php code:

$encrypt_val=base64_encode("value");

and on js just:

var my_orignal_val = window.atob(passed_val);

This will enough for your requirement.

Solution 4:

You are using two libraries that try to accommodate input that is - strictly speaking - not valid. Rijndael requires keys that 16, 24 or 32 bytes long random byte strings. You provide a 13 character string. Mcrypt, the PHP library, uses the string (presumable utf8 encoded) directly as binary input and zero pads it to the required 32 bytes for MCRYPT_RIJNDAEL_256. CryptoJS on the other hand decides that you have entered something like a passphrase and instead uses a key derivation function to generate a 32 byte key.

Furthermore the encryption algorithms used don't even match. Mcrypt uses a seldom implemented variant of the original Rijndael for the 256 bit version, while CryptoJS implements the widely known variant AES256 of the Rijndael proposal. The 128 bit version of both (MCRYPT_RIJNDAEL_128 and AES128) are identical though.

The third problem you are about to face later is that Mcrypt also uses a crazy padding scheme for the data being encrypted. As Rijndael is a block cipher, it can only encrypt blocks of 16, 24 or 32 bytes (depending on the variant - AES always uses 16 byte blocks). As such data has to be padded. Mcrypt does this in a non-injective way by just appending zeros. If you are only encoding strings this will not be so much of a problem for you as utf8 encoded strings never contain zero bytes, so you can just strip them off (CryptoJS even supports that natively).

The easiest fix to all these problems is to avoid having to implement any cryptography yourself (it is strongly discouraged anyway without a wide knowledge of the subject). Can you instead transmit your sensitive information over https which will use TLS (formerly called SSL) to encrypt and authenticate the channel?