12

I have this mcrypt_encrypt call, for a given $key, $message and $iv:

$string = mcrypt_encrypt(MCRYPT_3DES, $key, $message, MCRYPT_MODE_CBC, $iv);

I'd like to change the mcrypt_encrypt call to an openssl_encrypt one, to future-proof this.

By having $mode = 'des-ede3-cbc' or $mode = '3DES'; and $options = true I get the more similar response, but not identical. Is there other way to call it to get a perfect match?

I am getting this (base64_encoded) for a lorem-ipsum $message+$key combinations, so I am starting to believe one function or the other are padding somewhat the message before encrypting...

for mcrypt:

"Y+JgMBdfI7ZYY3M9lJXCtb5Vgu+rWvLBfjug2GLX7uo="

for for openssl:

"Y+JgMBdfI7ZYY3M9lJXCtb5Vgu+rWvLBvte4swdttHY="

Tried using $options to pass OPENSSL_ZERO_PADDING, but passing anything but 1 (OPENSSL_RAW_DATA, or true) results in an empty string ...

Neither using OPENSSL_ZERO_PADDING nor OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING work... :( I'm using "OpenSSL 1.0.2g 1 Mar 2016".

Already read this q&a, but it does not help me. Not the only one with padding troubles, but no solution in sight so far. Second answer talks about adding padding to mcrypt call, I would really want to remove padding from openssl encryption call...

TheTom
  • 934
  • 2
  • 14
  • 40
yivi
  • 42,438
  • 18
  • 116
  • 138
  • You should never get a perfect match, EVER. That's what initialization vector is for. Every time you encrypt the **same** payload with the **same** algorithm and the **same** key, you should get completely different output if you want to be safe. If you get the same output for same input, your encryption is weak. That's the point of IV in encryption. Once encrypted, you deliver encrypted payload with IV to the other side. – Mjh Dec 16 '16 at 10:41
  • 1
    If I'm using the same payload, same algo, same IV; I reckon I should be getting the same output. Never mind that that I should use a different IV for each call: using the same IV for both calls (mcrypt and openssl), I think I should get the same output, right? I'm starting to believe is related to message padding, since the begging of the output is the same. – yivi Dec 16 '16 at 10:45
  • If everything is the same then you should be getting the same output, correct. 4th parameter for `openssl_encrypt` controls the padding. You can encrypt with `$encrypted = openssl_encrypt($data, $alg, $key, OPENSSL_ZERO_PADDING, $iv);` and check if you get the same output. At [www.php.net/openssl_encrypt](http://www.php.net/openssl_encrypt), you can read the comments to see how to use 4th parameter. – Mjh Dec 16 '16 at 10:48
  • `OPENSSL_ZERO_PADDING` doesn't work, at least for me ... but yes - padding is why you can't get the same result. However, there's so many thinks wrong with your encryption scheme, that you might as well just replace it with a new one - please use a library for that, don't roll your own. – Narf Dec 16 '16 at 10:51
  • Right now is about to understand how to make this call and get the same results than with mcrypt.Thanks for your comments on encryption. This is neither the full solution, nor I have the authority to change everything I'd want in this codebase. – yivi Dec 16 '16 at 10:56
  • Well, that's the problem - properly padding the data yourself is key to getting the same results, and if you do that you *are* changing the encryption scheme. I know, you're trying to avoid BC breaks, but that's not possible here, so you should do yourself a favor and do the entire thing properly. – Narf Dec 16 '16 at 12:09
  • I guess is not solvable then. I need to create this message to compare with a verification messaged provided by a third party (for which I have a $key and $iv provided separately). I have no control over this third party encryption methods, I can only check against the encrypted message they provide, which apparently matches mcrypt response, but not openssl's. If OPENSSL_ZERO_PADDING worked, all would be dandy. – yivi Dec 16 '16 at 12:20
  • Please read [this q&a](http://stackoverflow.com/questions/30475946/mcrypt-encrypt-not-working-properly-on-php-5-6-9), some things changed regarding the padding in the IV and you should in any case use `mcrypt_create_iv()`. – Daniel W. Jan 16 '17 at 09:46
  • It is best not to use mcrypt, it has been abandonware for nearly a decade now. It has therefore been deprecated and will be removed from the core and into PECL in PHP 7.2. It does not support standard PKCS#7 (née PKCS#5) padding, only non-standard null padding that can't even be used with binary data. mcrypt has many outstanding [bugs](https://sourceforge.net/p/mcrypt/bugs/) dating back to 2003. Instead consider using [defuse](https://github.com/defuse/php-encryption) or [RNCryptor](https://github.com/RNCryptor), they provide a complete solution, are being maintained and is correct. – zaph Jan 16 '17 at 13:44
  • This question is about not using mcrypt. – yivi Jan 16 '17 at 13:45
  • Also see [Upgrading my encryption library from Mcrypt to OpenSSL](http://stackoverflow.com/q/43329513/608639) and [Preparing for removal of Mcrypt in PHP 7.2](http://stackoverflow.com/q/42696657/608639) – jww Apr 21 '17 at 17:48

2 Answers2

37

mcrypt_encrypt zero-pads input data if it's not a multiple of the blocksize. This leads to ambiguous results if the data itself has trailing zeroes. Apparently OpenSSL doesn't allow you to use zero padding in this case, which explains the false return value.

You can circumvent this by adding the padding manually.

$message = "Lorem ipsum";
$key = "123456789012345678901234";
$iv = "12345678";

$message_padded = $message;
if (strlen($message_padded) % 8) {
    $message_padded = str_pad($message_padded,
        strlen($message_padded) + 8 - strlen($message_padded) % 8, "\0");
}
$encrypted_mcrypt = mcrypt_encrypt(MCRYPT_3DES, $key,
    $message, MCRYPT_MODE_CBC, $iv);
$encrypted_openssl = openssl_encrypt($message_padded, "DES-EDE3-CBC", 
    $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);

printf("%s => %s\n", bin2hex($message), bin2hex($encrypted_mcrypt));
printf("%s => %s\n", bin2hex($message_padded), bin2hex($encrypted_openssl));

This prints both as equal.

4c6f72656d20697073756d => c6fed0af15d494e485af3597ad628cec
4c6f72656d20697073756d0000000000 => c6fed0af15d494e485af3597ad628cec
yivi
  • 42,438
  • 18
  • 116
  • 138
Joe
  • 1,656
  • 11
  • 10
-3

mcrypt_encrypt uses zeroes to pad message to the block size. So you can add zeroes to the tail of your raw data, and then encrypt the block.

Using OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING should work. If it doesn't, then you can remove padding from the decrypted data by yourself.

James Skemp
  • 8,018
  • 9
  • 64
  • 107
Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48
  • Why should I spend time on that? – Nickolay Olshevsky Jan 16 '17 at 09:53
  • 1
    `OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING` do not work. Probably a bug in phps openssl implementation or something? Removing the extra characters from the unencrypted data doesn't work for me. This string is used as a verification signature: I'm provided with one and a some data, create this encrypted data to match the received signature. I don't control the system on the other side. Modifying mcrypt_ call so it matches openssl_ doesn't help me, I need openssl_ to match mcrypt_ :) – yivi Jan 16 '17 at 09:54
  • 1
    You can add zero padding by yourself, then call openssl_encrypt. This will add openssl's padding as well. Then remove that padding (last 8 bytes). Then output should match mcrypt_encrypt – Nickolay Olshevsky Jan 16 '17 at 10:00
  • 2
    I'm not sure I'm following. Can you post this as part of your answer, with a code example showing what you mean? Thanks. – yivi Jan 16 '17 at 10:50