Reputation: 1614
I can't for the life of me figure out how to migrate my legacy mcrypt code to OpenSSL. I got it working for Blowfish with CBC and for Rijndael with CBC, but Blowfish with ECB is eluding me.
And yes, I read Moving from mcrypt with Blowfish & ECB to OpenSSL and I tried zero-padding the data, not zero-padding the data, zero-padding the key, cycling over the key and any combination of them and nothing seems to work.
This is my code:
<?php
function encrypt_with_mcrypt($data, $key) {
return mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
}
function encrypt_with_openssl($data, $key) {
return openssl_encrypt($data, 'BF-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY);
}
$data = 'foobar';
$key = 'supersecretkey';
var_dump(base64_encode(encrypt_with_mcrypt($data, $key)));
var_dump(base64_encode(encrypt_with_openssl($data, $key)));
And this is the output:
test.php:13:
string(12) "5z0q3xNnokw="
test.php:14:
string(12) "1zyqavq7sCk="
Upvotes: 4
Views: 1653
Reputation: 17383
I do not have much to add to Maarten's answer, except I thought it would be nice to show some the code that illustrates his words.
mcrypt
adds zeroes to fill up the plaintext to a multiple of the BF blocksize of 8 bytes, which can be shown by printing hexdumps of both the plaintext and the decrypted ciphertext:
$key = "supersecretkey";
$data = "foobar";
$ctxt = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
$ptxt = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $ctxt, MCRYPT_MODE_ECB);
echo bin2hex($data).PHP_EOL;
echo bin2hex($ptxt).PHP_EOL;
gives the following hexdumps:
666f6f626172
666f6f6261720000
openssl
by default uses PKCS#5 padding which, in this case, adds 2 bytes with value 2 at the end of the block:
$key = "supersecretkey";
$data = "foobar";
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY;
$ctxt = openssl_encrypt($data, 'BF-ECB', $key, $opts);
$ptxt = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $ctxt, MCRYPT_MODE_ECB);
echo bin2hex($data).PHP_EOL;
echo bin2hex($ptxt).PHP_EOL;
gives
666f6f626172
666f6f6261720202
The ciphertext for mcrypt
and openssl
can be made consistent by manually adding the padding bytes. Note the OPENSSL_ZERO_PADDING
options and the addition of "\0\0"
:
$key = "supersecretkey";
$data = "foobar";
$ctxt_mc = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data, MCRYPT_MODE_ECB);
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY | OPENSSL_ZERO_PADDING;
$ctxt_os = openssl_encrypt($data."\0\0", 'BF-ECB', $key, $opts);
echo bin2hex($ctxt_mc).PHP_EOL;
echo bin2hex($ctxt_os).PHP_EOL;
gives:
e73d2adf1367a24c
e73d2adf1367a24c
Alternatively, manually inserting PKCS#5 padding bytes at the end when using mcrypt
:
$key = "supersecretkey";
$data = "foobar";
$ctxt_mc = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, $data."\2\2", MCRYPT_MODE_ECB);
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY;
$ctxt_os = openssl_encrypt($data, 'BF-ECB', $key, $opts);
echo bin2hex($ctxt_mc).PHP_EOL;
echo bin2hex($ctxt_os).PHP_EOL;
gives
d73caa6afabbb029
d73caa6afabbb029
Finally, trying to invoke openssl_encrypt()
with padding disabled and a length that is not a multiple of the block size:
$key = "supersecretkey";
$data = "foobar";
$opts = OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY | OPENSSL_ZERO_PADDING;
$ctxt = openssl_encrypt($data, 'BF-ECB', $key, $opts);
echo(openssl_error_string().PHP_EOL)
gives
error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:data not multiple of block length
Remark: the name OPENSSL_ZERO_PADDING
is confusing, but it means "no padding". You might be tempted to use the flag OPENSSL_NO_PADDING
, but that one is not intended to be used with openssl_encrypt()
. Its value is 3, which is the same as OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
. In stead, it is intended for use with asymmetric cryptography.
Upvotes: 4
Reputation: 94038
The mcrypt library / wrapper defaults to zero-byte padding (only when required) while OpenSSL library / wrapper defaults to PKCS#5 padding. That means that the single block gets padded differently and will therefore show a different block of ciphertext.
A common trick is to decrypt the resulting ciphertext without any unpadding and then check the padding bytes by viewing the plaintext + padding in hexadecimals.
This will show you:
5z0q3xNnokw=
666f6f6261720000
for mcrypt and
1zyqavq7sCk=
666f6f6261720202
for OpenSSL.
Using a larger plaintext message that requires multiple blocks to be encrypted would also have shown you that encryption is going fine except for the last block.
First zero-pad your data if and only if the mcrypt input is not a multiple of 8 bytes (the block size of Blowfish), then use OPENSSL_ZERO_PADDING
as the padding mode.
Note that looking at the source code shows that OPENSSL_ZERO_PADDING
for some unspecified reason seems to mean "no padding" for the wrapper and OPENSSL_NO_PADDING
does seem to conflict with other settings - this I regard as a rather bad design and implementation mistake by the developers of the PHP OpenSSL wrapper API.
More info can be found by the great research performed by Reinier that shows how the API pads / unpads (or forgets to pad / unpad, depending on where you stand).
Upvotes: 4