Use Openssl_Encrypt to Replace Mcrypt for 3Des-Ecb Encryption

Use openssl_encrypt to replace Mcrypt for 3DES-ECB encryption

now I want to encrypt it use openssl_encrypt, and I did not find des3-ecb in openssl_get_cipher_methods() list.

It's des-ede3. Symmetric encryption with a block cipher needs some kind of mode of operation. If you look through the list, you will see something like des-ede3, des-ede3-cbc, des-ede3-cfb and des-ede3-ofb. CBC, CFB and OFB are all named and the unnamed cipher must be the only other common mode of operation: ECB.


Never use ECB mode. It's deterministic and therefore not semantically secure. You should at the very least use a randomized mode like CBC or CTR. It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.

Don't use Triple DES nowadays. It only provides at best 112 bit of security even if you use the largest key size of 192 bit. If a shorter key size is used, then it only provides 56 or 57 bits of security. AES would be faster (processors have a special AES-NI instruction set) and even more secure with the lowest key size of 128 bit. There is also a practical limit on the maximum ciphertext size with 3DES. See Security comparison of 3DES and AES.

PHP 7.2 openssl_encrypt and mcrypt_encrypt generate different values

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.

openssl_encrypt VS mcrypt_encrypt

Two problems:

  1. You aren't Base64 decoding the key, so you're passing a 24-byte (= 192-bit) key to both openssl_encrypt and mcrypt_encrypt. Apparently, these functions interpret such a key in different ways! base64_decode the key first for consistent results.

    Alternatively, if you really want to use the Base64-encoded string as a 192-bit key, pass 'aes-192-cbc' as the method to openssl_encrypt(). This is what mcrypt is doing here. (Which is not the same as what would happen if you passed MCRYPT_RIJNDAEL_192 as the cipher -- that changes the block size, not the key size!)

  2. openssl_encrypt uses PKCS5 padding automatically. Padding the data before passing it to this function ends up making the data get padded twice, leaving it one block longer than intended.

With these problems fixed, both functions now give the same result.

Convert mcrypt_generic to openssl_encrypt

Mcrypts: MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC

is equivalent to:

OPENSSL: AES-256-CBC

I have no idea why there's the difference, but changing the 128 to 256 solved it for me.

PHP OpenSSL encrypt with DES-ECB algorithm unexpected output

What must be considered for the website?

The website allows encryption with DES and TripleDES in 2TDEA variant (double length keys). The algorithm is determined by the key size: For an 8 bytes key DES is used, for a 16 bytes key TripleDES/2TDEA. The supported modes are ECB and CBC.

Key, IV (in case of CBC mode), plaintext and ciphertext have to be entered hex encoded on the website. Zero padding is applied (the variant that does not pad if the plaintext length is already an integer multiple of the DES/TripleDES block size of 8 bytes).

What does this mean for the posted data?

Since the posted hex encoded key 96187BBAB2BD19ACF899B74FB4E37972 has a size of 16 bytes, TripleDES/2TDEA is used (in ECB mode).

The hex encoded plaintext F01DCCE40F8C365ADE0A7DC03BC11DDE has a size of 16 bytes and is therefore not padded. The resulting hex encoded ciphertext 355A627E977D8ECF4953C98D801E472F thus has the same length.

How can the PHP code be fixed?

The posted PHP code gives a different result because DES is used as algorithm (in ECB mode) and also the hex encoding/decoding is missing. Thus to produce the result of the website, the encoding/decoding has to be fixed and DES-EDE-ECB has to be applied as algorithm (not to be confused with DES-EDE3-ECB for 3TDEA/triple length keys):

$data = hex2bin("F01DCCE40F8C365ADE0A7DC03BC11DDE");                                                // hex decode the 16 bytes data
$key = hex2bin("96187BBAB2BD19ACF899B74FB4E37972"); // hex decode the 16 bytes key key
$encrypted = openssl_encrypt($data, 'DES-EDE-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); // apply DES-EDE-ECB; use OPENSSL_ZERO_PADDING
echo bin2hex($encrypted); // 355a627e977d8ecf4953c98d801e472f // hex encode the 16 bytes ciphertext

with the output:

355a627e977d8ecf4953c98d801e472f

that is equal to the result of the web site, s. here



How can a consistent result be achieved for DES?

The current PHP code applies DES and therefore implicitly truncates the key to 8 bytes by using only the first 8 bytes. This can be easily verified by removing the last 8 bytes of the key. The result does not change.

If the shortened key is applied to the website, the results match again.

$data = hex2bin("F01DCCE40F8C365ADE0A7DC03BC11DDE");                                            // hex decode the 16 bytes data
$key = hex2bin("96187BBAB2BD19AC"); // hex decode the 8 bytes key
$encrypted = openssl_encrypt($data, 'DES-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); // apply DES-ECB; use OPENSSL_ZERO_PADDING
echo bin2hex($encrypted); // 692370230b8176f011760ddc07392a4f // hex encode the 16 bytes ciphertext

with the output:

692370230b8176f011760ddc07392a4f

that is equal to the result of the web site, s. here.



Related Topics



Leave a reply



Submit