Encrypt with CryptoJS and decrypt with PHP

25,443

Solution 1

You're not doing the same thing on both sides.

IV

You did parse the IV in CryptoJS, but forgot to do it in PHP:

$iv_dec = pack('H*', "101112131415161718191a1b1c1d1e1f");

To fix that your IV is wrong, you probably noticed that the first 16 bytes are gibberish. That happens when the IV is wrong. Note that CryptoJS uses CBC mode by default, so the IV has only influence on the first block during decryption. Remove this:

$ciphertext_dec = substr($ciphertext_dec, 16);

Padding

You probably noticed that most plaintexts don't come out right. They end with some strange repeated characters at the end. This is the PKCS#7 padding that is applied by default in CryptoJS. You have to remove the padding yourself in PHP. Good thing is that Maarten Bodewes has provided a proper copy paste solution for this here.

trim() might be appropriate for ZeroPadding, but not when a proper padding scheme like the one defined in PKCS#7 is used. You may remove the trim() call altogether, because it is not useful and may result in unexpected plaintext, becauses zero bytes and whitespace is trimmed from the beginning and end.

Solution 2

Hello,

in order to achieve this it should be considered to use the key and iv with 32 hex digits each, i had to solve exactly this doing my stuff and here is the way it goes

<!--
This reach.your.crypto.js is just a ficticious placeholder, 
that was used replaced by http://crypto-js.googlecode.com/svn/tags/3.1.2/build/,
which does not exist anymore,
which is the path to your CryptoJS library,
that can be downloaded through 
https://code.google.com/archive/p/crypto-js/downloads?page=1
-->
<script src="reach.your.crypto.js/rollups/aes.js">
</script>

<script type="text/javascript">
//The key and iv should be 32 hex digits each, any hex digits you want, 
//but it needs to be 32 on length each
var key = CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef");
var iv =  CryptoJS.enc.Hex.parse("abcdef9876543210abcdef9876543210");

/*
if you wish to have a more friendly key, you can convert letters to Hex this way:
var a = "D";
var hex_D = a.charCodeAt(0).toString(16);
just to mention,
if it were to binary, it would be:
var binary_D = a.charCodeAt(0).toString(2);
*/

var secret = "Hi, this will be seen uncrypted later on";

//crypted
var encrypted = CryptoJS.AES.encrypt(secret, key, {iv:iv});
//and the ciphertext put to base64
encrypted = encrypted.ciphertext.toString(CryptoJS.enc.Base64);    
//Assuming you have control on the server side, and know the key and iv hexes(we do),
//the encrypted var is all you need to pass through ajax,
//Let's follow with welcomed pure JS style, to reinforce one and other concept if needed
var xh = new XMLHttpRequest();
xh.open("POST", "decrypt_in_php.php", true);
xh.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xh.send("encrypted="+encodeURIComponent(encrypted));
</script>

And now receiving and decrypting in PHP

<?php
//Here we have the key and iv which we know, because we have just chosen them on the JS,
//the pack acts just like the parse Hex from JS

$key = pack("H*", "0123456789abcdef0123456789abcdef");
$iv =  pack("H*", "abcdef9876543210abcdef9876543210");

//Now we receive the encrypted from the post, we should decode it from base64,
$encrypted = base64_decode($_POST["encrypted"]);
$shown = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv);

echo $shown;
//Although the decrypted is shown, there may be needed to trim and str_replace some \r \n \x06 \x05, if there is not a better "trim" way to do it though
?>

With this we will have back the "Hi, this will be seen uncrypted later on" :)

Solution 3

Here is a solution based on this comment, using openssl_decrypt from PHP.

The JavaScript part (development with NodeJS for browsers) — first, install CryptoJS with npm install crypto-js, then your JS code:

import aes from 'crypto-js/aes'
import encHex from 'crypto-js/enc-hex'
import padZeroPadding from 'crypto-js/pad-zeropadding'

// message to encrypt
let msg = "Hello world";

// the key and iv should be 32 hex digits each, any hex digits you want, but it needs to be 32 on length each
let key = encHex.parse("0123456789abcdef0123456789abcdef");
let iv =  encHex.parse("abcdef9876543210abcdef9876543210");

// encrypt the message
let encrypted = aes.encrypt(msg, key, {iv:iv, padding:padZeroPadding}).toString();

// and finally, send this "encrypted" string to your server

On the PHP side, your code will look like that:

// we use the same key and IV
$key = hex2bin("0123456789abcdef0123456789abcdef");
$iv =  hex2bin("abcdef9876543210abcdef9876543210");

// we receive the encrypted string from the post
$encrypted = $_POST['decrypt'];
$decrypted = openssl_decrypt($encrypted, 'AES-128-CBC', $key, OPENSSL_ZERO_PADDING, $iv);
// finally we trim to get our original string
$decrypted = trim($decrypted);
Share:
25,443
Papauha
Author by

Papauha

Updated on May 06, 2020

Comments

  • Papauha
    Papauha about 4 years

    On the client side (mobile device) I encrypt a users password with CryptoJS:

    var lib_crypt = require('aes');
    
    $.loginButton.addEventListener('click', function(e){
    
    var key = lib_crypt.CryptoJS.enc.Hex.parse('bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3');
    var iv  = lib_crypt.CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
    
    var encrypted = lib_crypt.CryptoJS.AES.encrypt($.passwordInput.value, key, { iv: iv });
    
    var password_base64 = encrypted.ciphertext.toString(lib_crypt.CryptoJS.enc.Base64); 
    return password_base64; 
    });
    

    On the server side i want to decrypt it with mcrypt_decrypt:

    function decryptPassword($password)
    {
        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
        $ciphertext_dec = base64_decode($password);
        $iv_dec = "101112131415161718191a1b1c1d1e1f";
    
        $ciphertext_dec = substr($ciphertext_dec, 16);
        $decryptedPassword = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
    
        return trim($decryptedPassword);
    }
    

    I use the same key and IV, what do I do wrong?