JSGarcia
JSGarcia

Reputation: 566

PHP 7.2 openssl_encrypt and mcrypt_encrypt generate different values

I am migrating all the functions of my project that encrypt data and work with mcrypt to openssl.

Making tests I find that, encrypting the same data with the same keys I get different results.

When decrypting I get the correct result with boths functions; the problem is that I share this information with an external provider and only successfully decrypt the data if I encrypt with mcrypt.

This is the test code:

// Configuration.
$data = 'FOO';
$secret = '111222333444555666777888';
$iv = 'ABCDEFGH';

// Encrytp & decrypt with mcrypt.
$encMcrypt = bin2hex(mcrypt_encrypt(MCRYPT_3DES, $secret, utf8_encode($data),  MCRYPT_MODE_CBC, $iv));
$decMcrypt = mcrypt_decrypt(MCRYPT_3DES, $secret, hex2bin($encMcrypt), MCRYPT_MODE_CBC, $iv);

// Encrytp & decrypt with openssl.
$encOpenSSL = bin2hex(openssl_encrypt(utf8_encode($data), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA, $iv));
$decOpenSSL = openssl_decrypt(hex2bin($encOpenSSL), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA, $iv);

// Result.
echo "mcrypt encrypt: $encMcrypt <br>";
echo "openssl encrypt: $encOpenSSL <br>";
echo "mcrypt decrypt: $decMcrypt <br>";
echo "openssl decrypt: $decOpenSSL";

Result:

mcrypt encrypt: 3f9bd8d5f844ff67
openssl encrypt: b2f4b9aeb07e1ca4
mcrypt decrypt: FOO
openssl decrypt: FOO

Does anyone know why it is because they get different results?

Thank you!

Upvotes: 2

Views: 3054

Answers (3)

Topaco
Topaco

Reputation: 49141

The difference is that mcrypt_encrypt / mcrypt_decrypt uses Zero-Padding and openssl_encrypt / openssl_decrypt uses PKCS7-Padding. This can be easily verified by applying Zero-Padding for openssl: For this, PKCS7-Padding must be disabled with the flag OPENSSL_ZERO_PADDING (important: despite the name, this flag doesn't mean that Zero-Padding is used, but that no padding is applied at all), and the plaintext must be padded with 0-values to the next integer multiple of the blocksize (8 bytes for Triple-DES) unless the length already corresponds to an integer multiple of the blocksize:

<?php

function zeroPadding($data, $size) {
    $oversize = strlen($data) % $size;
    return $oversize == 0 ? $data : ($data . str_repeat("\0", $size - $oversize)); 
}

// Something is wronguration.
$data = 'FOO';

$secret = '111222333444555666777888';
$iv = 'ABCDEFGH';

// Encrytp & decrypt with mcrypt.
$encMcrypt = bin2hex(mcrypt_encrypt(MCRYPT_3DES, $secret, utf8_encode($data), MCRYPT_MODE_CBC, $iv));
$decMcrypt = mcrypt_decrypt(MCRYPT_3DES, $secret, hex2bin($encMcrypt), MCRYPT_MODE_CBC, $iv);

// Encrytp & decrypt with openssl.
$encOpenSSLZeroPadding = bin2hex(openssl_encrypt(utf8_encode(zeroPadding($data, 8)), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv));
$decOpenSSLZeroPadding = openssl_decrypt(hex2bin($encOpenSSLZeroPadding), 'DES-EDE3-CBC', $secret, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);

// Result.
echo "data padded: " . bin2hex(zeroPadding($data, 8)) . "<br>";
echo "mcrypt encrypt: $encMcrypt <br>";
echo "openssl encrypt: $encOpenSSLZeroPadding <br>";
echo "mcrypt decrypt: $decMcrypt <br>";
echo "mcrypt decrypt: " . bin2hex($decMcrypt) . "<br>";
echo "openssl decrypt: $decOpenSSLZeroPadding" . "<br>";
echo "openssl decrypt: " . bin2hex($decOpenSSLZeroPadding);

with the following output:

data padded: 464f4f0000000000
mcrypt encrypt: 3f9bd8d5f844ff67
openssl encrypt: 3f9bd8d5f844ff67
mcrypt decrypt: FOO
mcrypt decrypt: 464f4f0000000000
openssl decrypt: FOO
openssl decrypt: 464f4f0000000000

Instead of using a Zero-Padding in the openssl-context, a PKCS7-Padding could be used in the mcrypt-context. No matter which of the two variants is used, with identical padding mcrypt and openssl results are identical!

It should be noted (see the hexadecimal output) that mcrypt doesn't remove the Zero-Padding during decryption (unlike openssl which removes the PKCS7-Padding during decryption). Also, Zero-Padding is unreliable compared to PKCS7-Padding. If the requirements allow it (which in your case is probably not the case because of the external provider), PKCS7-Padding should therefore be used.

Furthermore, both posted variants use the same algorithm, namely Triple-DES in CBC-mode. Triple-DES has a blocksize of 8 bytes and a keysize of 24 bytes. Triple-DES is not identical to DES, but is based on DES in the sense that it consists of three DES-runs (encryption-decryption-encryption = ede). mcrypt specifies Triple-DES/CBC with two parameters, MCRYPT_3DES (Triple-DES) and MCRYPT_MODE_CBC (CBC-mode), while openssl uses only one parameter, DES-EDE3-CBC.

There are several Keying-Options for Triple-DES. 3TDEA uses three independent DES keys and is specified in the context of openssl with DES-EDE3-CBC, which expects a 24 bytes key, 2TDEA uses two independent keys and can be specified in the context of openssl alternatively with DES-EDE-CBC, which expects a 16 bytes key.

Triple-DES is much slower than the modern AES, but has a comparable security. As with padding, you should switch to AES if possible.

Upvotes: 3

SamOrSomething
SamOrSomething

Reputation: 11

Mcrypt and openssl are two different ciphers, so they encrypt and decrypt data in different ways.

You can use openSSL's des-ede3 method, however you should really be using AES: Security Comparison of AES and DES

If your external provider is using mcrypt_decrypt to decrypt the data you send them, then they won't be able to decrypt data that is encrypted with openssl, and vice-versa.

Your external provider will need to change the way the decrypt the data as well if you move to the more secure AES openSSL encryption.

(on a side note, it's good that you're moving to openssl - mcrypt is abandonware circa php 7.1)

Upvotes: 1

ahuemmer
ahuemmer

Reputation: 2049

As I see it, both methods use different algorithms, see this question. You should try and use des-ede3 as openssl cipher method, which is the equivalent of mcrypt's MCRYPT_3DES.

Upvotes: 1

Related Questions