3

I'm trying to decrypt some data that has been encrypted with a passphrase and aes-256-cbc method in a PHP script.

Here is how I encrypt the original data

printf "Hello" |   openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD"

// output
U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=

When I try to decrypt it in command-line it works fine

printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" |   openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD"

// output
Hello

BUT when I use openssl_decrypt() in my PHP script it doesn't work!!

$result = openssl_decrypt("U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=", 'AES-256-CBC', "MYPASSWORD");
var_dump($result);

//output
bool(false)

I append the following lines to get the error

while ($msg = openssl_error_string())
    echo $msg . "<br />\n";

And it returns:

error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

I know that I should use a key/iv pair but I am not able to extract it from my passphrase with any salt. How can I get it to make the following command work?

printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -K ??????????????? -iv ????????????????

// expected output !!!
Hello

EDIT:

I tried to get key/iv with -p argument but it doesn't work

printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD" -p
salt=9D5AE06E8A2B627C
key=8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA
iv =4150125DCCD36F73A9F08F3020151A04
Hello

printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -K 8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF05E67E2A8313FA -iv 4150125DCCD36F73A9F08F3020151A04
    bad decrypt
140735954895816:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:529:
??G?"r!C???&C&??
Reinier Torenbeek
  • 16,669
  • 7
  • 46
  • 69
B 7
  • 670
  • 7
  • 23

2 Answers2

4

There is a difference between the password (or passphrase) used as a parameter to openssl enc via the -k option (in your case "MYPASSWORD") and the key parameter that the PHP function openssl_decrypt() expects. The -k option to openssl enc is a passphrase of any length from which an actual 256 bits encryption key will be derived. That is also the key that the PHP openssl_decrypt() function needs. This encryption key is 256 bits because you have chosen aes-256.

You can get to know what that derived encryption key is by adding the -p option when invoking openssl enc. This also prints the iv, another parameter that you will need to use with the PHP openssl_decrypt() function. For example:

printf "Hello" |  openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD" -nosalt -p
key=E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F
iv =5A79774BB4B326EED949E6871FC27697
sp0z18QezUO8tSy7tgjOEw==

These printed key and iv values are the ones that you will need to feed into your PHP openssl_decrypt() function invocation, like this:

$ciphertext = 'sp0z18QezUO8tSy7tgjOEw==';
$key = hex2bin('E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F');
$iv = hex2bin('5A79774BB4B326EED949E6871FC27697');
$result = openssl_decrypt($ciphertext, 'AES-256-CBC', $key, 0, $iv);
var_dump($result);

Running the PHP script now results in success:

$ php decrypt.php 
string(5) "Hello"

You may have noticed the extra -nosalt option when running openssl enc. Salt is used to add some randomness/uniqueness to the key derivation process and -nosalt omits that step. As a result, the key, iv and ciphertext will be the same in every run (if the same passphrase and plaintext are used) and you should be able to exactly reproduce the output. If you do not use -nosalt, your experiment will still work but the key, iv and ciphertext values will be different for each run and you will also have to get rid of the salt that openssl adds as a header -- see further down this answer for the details.

Another option would be to let the PHP code derive the key and iv from the passphrase before invoking openssl_decrypt(). To do that, you will have to inspect the code of the enc tool for the openssl version that you are using. There you can see which key derivation function is used -- it depends on the version of openssl you are using as well as the options you are giving it -- and whether that is available in the PHP bindings of openssl.


Update, responding to your comment where you add the information that you only have the ciphertext and the passphrase available and that ciphertext was created with crypto-js.

Looking at the source code of crypto-js, it mentions in a comment in the source file evpkdf.js that "the key derivation function is meant to conform with EVP_BytesToKey", which is the same function that most openssl versions use. So you should be able to use the openssl enc tool to extract the key and the iv by using the -p option, like this:

$printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD" -p
salt=9D5AE06E8A2B627C
key=8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA
iv =4150125DCCD36F73A9F08F3020151A04

(which you have confirmed in another comment by now as well) and then use those when invoking the PHP function, as described above. Note that you will have to do this for every ciphertext separately, because the salt (and thus the key and iv) were chosen differently, randomly by crypto-js for each encryption action. To do this in PHP directly, see my previous remark: the required functionality does not seem to be available in its decrypt module.

You can verify that this works by feeding the key and iv into openssl enc when decrypting. However, there is a snag. When using a salt, the openssl way is to include that salt in the output, as you can see here:

$ printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" |  openssl base64 -d -A | hexdump -C
00000000  53 61 6c 74 65 64 5f 5f  9d 5a e0 6e 8a 2b 62 7c  |Salted__.Z.n.+b||
00000010  7e 33 bb 56 2f fe 5e fe  1d c7 c8 a9 1f f0 c5 27  |~3.V/.^........'|
00000020

The first 16 bytes of the output are the "magic" bytes Salted__ with the salt after that. This salt is normally read by the tool if you use a passphrase, but it is in the way if you decrypt with key and iv directly. So you will have to remove that header before feeding the bytes into openssl enc as ciphertext when decrypting, for example using tail like this:

printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" |  openssl base64 -d -A | tail -c +17 | openssl enc -d -aes-256-cbc -K 8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA -iv 4150125DCCD36F73A9F08F3020151A04
Hello

This one-liner first does the base64 decoding, then removes the 16 first bytes and then feeds the result into openssl enc, no longer needing the -base64 options because that has already been taken care of.

In PHP:

$ciphertext = 'U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=';
$ciphertext_decoded = base64_decode($ciphertext);
$ciphertext_nosalt = base64_encode(substr($ciphertext_decoded, 16));

$key = hex2bin('8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA');
$iv = hex2bin('4150125DCCD36F73A9F08F3020151A04');

$result = openssl_decrypt($ciphertext_nosalt, 'AES-256-CBC', $key, 0, $iv);
var_dump($result);

All that said, you would probably be better off moving away from the key derivation that openssl enc and crypto-js use and that relies on the proprietary mechanism implemented by the OpenSSL EVP_ByesToKey function. Even openssl enc now warns about this being deprecated .

Instead, start using a standard algorithm like PBKDF2. This is supported by more recent versions of openssl enc and I have spotted it in the source code of the crypto-js and PHP crypto modules as well (but have never used those myself). If you have a database of encrypted data that you need to keep, you can re-encrypt its contents one time, using the old approach to decrypt and the PKDBF2 approach to encrypt. Make sure to store the salts separately and not as one blob together with the ciphertext.

B 7
  • 670
  • 7
  • 23
Reinier Torenbeek
  • 16,669
  • 7
  • 46
  • 69
  • Hi @Reinier. Thank you for you help! I understand your answer but my problem is that I have some ciphertexts and its passphrase. I used CryptoJS to generate ciphertexts and I stored them in DB. And now I need to retrieve the original string in a PHP script. So I need to get key/iv from the passphrase. I can't generate a new ciphertext. It should be possible because _openssl_ in cmd-line is able to decrypt ciphertexts with only passphrase – B 7 Jul 30 '18 at 13:01
  • What `openssl` cmd-line command do you use to decrypt those ciphertexts with only the passphrase? – Reinier Torenbeek Jul 30 '18 at 13:40
  • `printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD"` – B 7 Jul 30 '18 at 14:24
  • OK, just add the `-p` option to that line and it will print the `key` and the `iv` that you need to use in your `PHP` script. I added this to the answer. – Reinier Torenbeek Jul 30 '18 at 14:28
  • I added a final remark about a better way forward... In a nutshell: stop using proprietary openssl stuff. – Reinier Torenbeek Jul 30 '18 at 15:59
  • I tried to add `-p` option but it doesn't work. I edited the original question to illustrate what I've done – B 7 Jul 31 '18 at 08:32
  • There were two problems with your command. The key for the `-k` option was missing a few bytes (your mistake) and the stored salt was in the way (I forgot about that). I have added the working command to my answer. – Reinier Torenbeek Jul 31 '18 at 14:11
  • It works fine! I just edit your answer to add PHP snippet. Thank you very much. – B 7 Aug 02 '18 at 09:41
1

The issue here is that you are not using EVP_BytesToKey. This is the OpenSSL KDF used to derive a key and IV from your password.

Note that it is insecure. You should prefer passing a hex key and IV directly to openssl enc.

Luke Joshua Park
  • 9,527
  • 5
  • 27
  • 44