Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue encrypting with PHP (openssl_encrypt), then decrypting with JS (CryptoJS)

My first time using CryptoJS, and I'm struggling to decrypt a string I encrypted using openssl_encrypt() in PHP.

PHP 5.6.13.0 and CryptoJS 3.1.2


First, my PHP:

$encryptHash = hash_pbkdf2("sha256", "0000", "secret", 1000, 32);
var_dump($encryptHash);

$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
var_dump(bin2hex($iv));

$encrypted = openssl_encrypt("hello! this is my string!", 'aes-256-cbc', $encryptHash, 0, $iv);
var_dump($encrypted);

$encrypted = base64_encode($encrypted.":".bin2hex($iv));
echo "\r\n".$encrypted;

This gives me the following output:

string(32) "59b6ab46d379b89d794c87b74a511fbd"
string(32) "0aaff094b6dc29742cc98a4bac8bc8f9"
string(44) "xHIxg1HDUOqyhBmAaU2Sx3ct8GaKaeE5w4d1KM1yuDw="

eEhJeGcxSERVT3F5aEJtQWFVMlN4M2N0OEdhS2FlRTV3NGQxS00xeXVEdz06MGFhZmYwOTRiNmRjMjk3NDJjYzk4YTRiYWM4YmM4Zjk=

Now my JS:

var encryptedString = "eEhJeGcxSERVT3F5aEJtQWFVMlN4M2N0OEdhS2FlRTV3NGQxS00xeXVEdz06MGFhZmYwOTRiNmRjMjk3NDJjYzk4YTRiYWM4YmM4Zjk=";

var key256Bits  = CryptoJS.PBKDF2("0000", "secret", { keySize: 128/32, iterations: 1000, hasher: CryptoJS.algo.SHA256 });
var keyAsHex = key256Bits.toString(CryptoJS.enc.Hex);

/* keyAsHex = "59b6ab46d379b89d794c87b74a511fbd" */

var rawData = atob(encryptedString);
var rawPieces = rawData.split(":");

var crypttext = rawPieces[0];
var iv = rawPieces[1];

/* crypttext = "xHIxg1HDUOqyhBmAaU2Sx3ct8GaKaeE5w4d1KM1yuDw=" */
/* iv = "0aaff094b6dc29742cc98a4bac8bc8f9" */

/* So far so good? */

var plaintextArray = CryptoJS.AES.decrypt(
  { ciphertext: CryptoJS.enc.Base64.parse(crypttext) },
  CryptoJS.enc.Hex.parse(keyAsHex),
  { iv: CryptoJS.enc.Hex.parse(iv) }
);

/* plaintextArray: d.WordArray.n.extend.init
    sigBytes: -67
    words: Array[8]
        0: 1419734786
        1: -2048883413
        2: -1709437124
        3: 736946566
        4: 718053567
        5: -64039355
        6: 1868905697
        7: -910423965 */

var output = CryptoJS.enc.Utf8.stringify(plaintextArray);

/* output = "" */

As you can see, my output is an empty string. Anyone attempted to do something similar? I'm stumped!

Edit

Turns out my key lengths were incorrect! Here's my working PHP (encrypt) and JS (decrypt) code:


PHP:

$encryptHash = hash_pbkdf2("sha256", "0000", "secret", 1000, 32, true);
var_dump($encryptHash);

$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-cbc"));
var_dump($iv);

$encrypted = openssl_encrypt("hello! this is a test!", "aes-256-cbc", $encryptHash, 0, $iv);
var_dump($encrypted);

$encrypted = base64_encode($encrypted.":".bin2hex($iv));
echo "\r\n".$encrypted;

Gives me the following:

string(32) "Y½FËy©ØyLçÀJQ▼¢▄▄êI╩öo§(NtÙת‼ç"
string(16) "àX§ $VÇ‼♣┘█²áÓßt"
string(44) "VIzzao8Wdo8HPM015v6c5Q77ervGUIVbL6ERKRXb0fU="

Vkl6emFvOFdkbzhIUE0wMTV2NmM1UTc3ZXJ2R1VJVmJMNkVSS1JYYjBmVT06ODU1ODE1MjAyNDU2ODAxMzA1ZDlkYmZkYTBlMGUxNzQ=

JS:

var encryptedString = "Vkl6emFvOFdkbzhIUE0wMTV2NmM1UTc3ZXJ2R1VJVmJMNkVSS1JYYjBmVT06ODU1ODE1MjAyNDU2ODAxMzA1ZDlkYmZkYTBlMGUxNzQ=";

var key256Bits  = CryptoJS.PBKDF2("0000", "secret", { keySize: 256/32, iterations: 1000, hasher: CryptoJS.algo.SHA256 });

var rawData = atob(encryptedString);
var rawPieces = rawData.split(":");

var crypttext = rawPieces[0];
var iv = CryptoJS.enc.Hex.parse(rawPieces[1]);

var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(crypttext)});

var plaintextArray = CryptoJS.AES.decrypt(
  cipherParams,
  key256Bits,
  { iv: iv }
);

var output = CryptoJS.enc.Utf8.stringify(plaintextArray);

/* output === 'hello! this is a test!' */
like image 600
Mike Avatar asked Sep 27 '22 18:09

Mike


1 Answers

TL;DR - Try using a 32 byte key rather than a 16 byte key.

After composing an earlier answer and ultimately deleting it, having disproven my own theory about this being a problem with padding :-), I am now fairly certain that the problem might just be to do with key lengths.

Whilst trying to reproduce your issue I couldn't to get the first block of ciphertext to be identical when generated using openssl_encrypt vs CryptoJS. Then I doubled the length of the key and it worked.

The key you're generating above is 32 characters, but only 16 bytes once converted, so try doubling that and see what happens.

FWIW, here is the PHP code I used to test key lengths:

$data = "hello! this is a test!";
$method = 'aes-256-cbc';
$key = '59b6ab46d379b89d794c87b74a511fbd59b6ab46d379b89d794c87b74a511fbd';
$iv = '0aaff094b6dc29742cc98a4bac8bc8f9';

$e = openssl_encrypt( $data, $method, hex2bin( $key ), 0, hex2bin( $iv ));

echo 'Ciphertext: [', bin2hex( base64_decode( $e )), "]\n";
echo 'Key:        [', $key, "]\n";
echo 'Cleartext:  [', openssl_decrypt( $e, $method, hex2bin( $key ), 0, hex2bin( $iv )), "]\n";

// Test with openssl on the command line as well, just to be sure!
file_put_contents( 'clear.txt', $data );

$exec = "openssl enc -$method -e -in clear.txt -out encrypted.txt -base64 -nosalt -K $key -iv $iv";
exec ($exec);
$out = file_get_contents( 'encrypted.txt' );
echo 'Ciphertext: [', bin2hex( base64_decode(trim($out))), "]\n";

And here is the compatible JavaScript, which I run using jsc on my Mac:

var data = "hello! this is a test!";
var key = '59b6ab46d379b89d794c87b74a511fbd59b6ab46d379b89d794c87b74a511fbd';
var iv = '0aaff094b6dc29742cc98a4bac8bc8f9';

var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(data), CryptoJS.enc.Hex.parse(key), { iv: CryptoJS.enc.Hex.parse(iv) });

print( 'Ciphertext: [' + encrypted.ciphertext + ']' );
print( 'Key:        [' + encrypted.key + ']' );

cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(encrypted.ciphertext.toString())});
var decrypted = CryptoJS.AES.decrypt(cipherParams, CryptoJS.enc.Hex.parse(key), { iv: CryptoJS.enc.Hex.parse(iv) });

print( 'Cleartext:  [' + decrypted.toString(CryptoJS.enc.Utf8) + ']');

These two chunks of code produce identical ciphertext regardless of the length of the input, which confirms that the padding strategy is compatible between the two libraries. However, if you halve the length of the keys, the ciphertext will no longer be identical, which obviously means decryption isn't going to be compatible either.

UPDATE

I just discovered that hash_pbkdf2() returns ASCII hex strings by default, so you should either convert $encryptHash to binary with hex2bin() before passing it to openssl_encrypt() or set the last parameter of hash_pbkdf2() to true to get raw output.

UPDATE 2

I have just confirmed that your code will work if you make the following changes:

In the PHP, change the key size from 32 to 64 bytes and add the raw output option when generating the key:

$encryptHash = hash_pbkdf2("sha256", "0000", "secret", 1000, 64, 1);

Change the key length from 128 to 256 bits in the JavaScript:

var key256Bits  = CryptoJS.PBKDF2("0000", "secret", { keySize: 256/32, iterations: 1000, hasher: CryptoJS.algo.SHA256 });

Hopefully those changes work when you try them.

like image 61
JamesG Avatar answered Oct 13 '22 23:10

JamesG