45

I'm having trouble with basic encryption/decryption. I've looked all around for a working example but haven't quite found a working example.

-I will be encrypting in php, decrypting with cryptojs for a small layer of security

<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js">
<?
$text = "this is the text here";
$key = "encryptionkey";

$msgEncrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
$msgBase64 = trim(base64_encode($msgEncrypted));

echo "<h2>PHP</h2>";
echo "<p>Encrypted:</p>";
echo $msgEncrypted;
echo "<p>Base64:</p>";
echo $msgBase64;
 ?>

<p>AES Decrypt</p>
<script> 
    var key = 'encryptionkey';
    var encrypted = "<?php echo $msgBase64 ?>";
    //tried  var base64decode = CryptoJS.enc.Base64.parse(encrypted); 
    var decrypted = CryptoJS.AES.decrypt(encrypted, key);
    console.log( decrypted.toString(CryptoJS.enc.Utf8) );
</script>

Which step am I missing?

Alex Kulinkovich
  • 4,408
  • 15
  • 46
  • 50
user2769
  • 459
  • 1
  • 5
  • 3

5 Answers5

79

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

Note 2023: Improved version with namespace and newest PHP version support is in the GitHub Repository linked above. The code bellow is the first iteration, which is somehow outdated.

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);
Brain Foo Long
  • 2,057
  • 23
  • 28
  • Looks pretty good. But why did you choose to not use IV/Salt? – JSON Dec 21 '14 at 07:26
  • BTW, just want to add, it's amazing that you just posted this... exactly what I was looking for because Mcrypt is GPL, where OpenSSL is Apache/BSD – JSON Dec 21 '14 at 07:29
  • 1
    @JSON I wanted to keep the example as short as possible - It should show how the basic flow works. I'm also not a crypto expert and i'm not sure what a custom IV and custom salt should help. Salt and IV exist and is always different because salt is randomly generated and IV results from the passphrase and random salt. But when you can improve that code please do it on my GIT repos. – Brain Foo Long Dec 21 '14 at 10:25
  • @BrainFooLong Is there any licenses on the code above? Just wanna make sure in case I wanna distribute the code with my other stuff. – php_coder_3809625 May 18 '15 at 12:48
  • @php_coder_3809625 GPL v3 - I'll add it to the GitHub repos readme. – Brain Foo Long May 18 '15 at 13:42
  • can any way to get static encrypted string instead of random encrypted string by using this method..? – ReNiSh AR Aug 25 '15 at 18:05
  • @BrainFooLong After trying to get my own code to work **all day**, finding that's you'd spent time writing this really helped. And then it actually worked and now i'm speechless XD – User Jul 30 '16 at 13:02
  • Hii, Above php code is not working for encrypt and decrypt from received data through cryptojs – Gaurang Sondagar May 23 '19 at 06:48
16

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))
Jim Flood
  • 8,144
  • 3
  • 36
  • 48
  • Thanks! I wasn't able to get your example working unfortunately. When I removed the "padding: CryptoJS.pad.ZeroPadding", It would sortof work -- but adds "���" to the end of string. Also, I was wondering how to change the encrpytion key in your example. What does "\u0000\u0000\u0000" mean and how do I determine how much I need? – user2769 Jun 22 '14 at 17:20
  • "this is the text here" encoded in UTF-16 would be 42 bytes, which would be padded to 48 for encryption. Those extra 6 bytes would appear as three UTF-16 chars at the end, which would explain three funny chars. Try CryptoJS.pad.NoPadding, and look at the binary value of those last bytes. Are they all 0x00, or all 0x06? – Jim Flood Jun 22 '14 at 18:03
  • Each \u0000 is adding a zero byte 0x00, or two zero bytes if the string is UTF-16. Mcrypt must be padding the key to 16, 24, or 32 bytes. If this is ASCII or UTF-8, it would be 16 bytes. If UTF-16, then 32 bytes. You should be using a stronger key. Pick a key size of 16, 24, or 32 bytes and make it from cryptographically strong random data. – Jim Flood Jun 22 '14 at 18:06
  • `MCRYPT_RAND` is not a CSPRNG. You want a CSPRNG for IVs in CBC mode. Also, http://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken – Scott Arciszewski May 12 '15 at 12:08
  • The updates to your answer are visible from the revisions link. – Braiam Jun 01 '18 at 13:55
  • Stop editing my answer for frivolous reasons or I will delete it. Sign your graffiti. – Jim Flood Jun 28 '19 at 22:21
6

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.

Kashif Anwar
  • 180
  • 1
  • 12
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?

Perseids
  • 12,584
  • 5
  • 40
  • 64
-2

Encryption Javascript

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script>
        function encrypt(str) {
  var encoded = "";
  for (i=0; i<str.length;i++) {
    var a = str.charCodeAt(i);
    var b = a ^ 51;    // bitwise XOR with any number, e.g. 123
    encoded = encoded+String.fromCharCode(b);
  }
  return btoa(encoded);
}

PHP Decrypt

 function decrypt($encoded) {
$encoded =base64_decode($encoded);
  $decoded = "";
  for( $i = 0; $i < strlen($encoded); $i++ ) {
    $b = ord($encoded[$i]);
    $a = $b ^ 51;  // <-- must be same number used to encode the character
    $decoded .= chr($a);
  }
  return $decoded;
}

KEY -PHP Decrypt

function decrypt($passphrase, $enc_text)
    {
        $enc_text = json_decode($enc_text, true);
        try {
            $slam_ol = hex2bin($enc_text["slam_ltol"]);
            $iavmol  = hex2bin($enc_text["iavmol"]);
        } catch (Exception $e) {
            return null;
        }
        $ciphertext = base64_decode($enc_text["amtext"]);
        $iterations = 999;
        $key = hash_pbkdf2("sha512", $passphrase, $slam_ol, $iterations, 64);
        $decrypted = openssl_decrypt($ciphertext, 'aes-256-cbc', hex2bin($key), OPENSSL_RAW_DATA, $iavmol);
        return $decrypted;
    }

KEY -javascript encrypt

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script>
        function encrypt(passphrase, plain_text) {
            var slam_ol = CryptoJS.lib.WordArray.random(256);
            var iv = CryptoJS.lib.WordArray.random(16);
            var key = CryptoJS.PBKDF2(passphrase, slam_ol, {
                hasher: CryptoJS.algo.SHA512,
                keySize: 64 / 8,
                iterations: 999
            });
            var encrypted = CryptoJS.AES.encrypt(plain_text, key, {
                iv: iv
            });
            var data = {
                amtext: CryptoJS.enc.Base64.stringify(encrypted.ciphertext),
                slam_ltol: CryptoJS.enc.Hex.stringify(slam_ol),
                iavmol: CryptoJS.enc.Hex.stringify(iv)
            }
            return JSON.stringify(data);
        }

Amol
  • 55
  • 4
  • 1
    This does not answer the question, the question was to encrypt in PHP, decrypt in JavaScript, not the other way around – Omar Tarek Dec 15 '22 at 10:32