Use openssl_encrypt to replace Mcrypt for 3DES-ECB encryption
now I want to encrypt it use
openssl_encrypt
, and I did not finddes3-ecb
inopenssl_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:
You aren't Base64 decoding the key, so you're passing a 24-byte (= 192-bit) key to both
openssl_encrypt
andmcrypt_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 toopenssl_encrypt()
. This is what mcrypt is doing here. (Which is not the same as what would happen if you passedMCRYPT_RIJNDAEL_192
as the cipher -- that changes the block size, not the key size!)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
How to Count Same Values in an Array and Store It to a Variable
Get Coupon Data from Woocommerce Orders
Applied Coupons Disable Free Shipping Conditionally in Woocommerce
Fastest Hash for Non-Cryptographic Uses
Correct Indentation of HTML and PHP Using Vim
Create an Order Programmatically with Line Items in Woocommerce 3+
How to Populate HTML Dropdown List with Values from Database
How Effective Is the Honeypot Technique Against Spam
What Is the PHP Shorthand For: Print Var If Var Exist
What Does the B in Front of String Literals Do
Trying to Access Array Offset on Value of Type Null
Convert Number to Letter with PHP
Doctrine - How to Print Out the Real SQL, Not Just the Prepared Statement
Symfony2 Routing - Route Subdomains
How to Create a Twig Custom Tag That Executes a Callback