Search code examples
phpencryptionopensslmcryptblowfish

PHP mcrypt to openssl BF-CBC: how to get the same encrypted value


Need to replace existing encrypt and decrypt functions that use mcrypt blowfish cbc mode with it's openssl equivalent.

It needed to be able to handle the older values so the methods need to stay compatible.

Have the decrypt working, and the encrypt is 'almost' ok, but not quite there.

This is the code I have:

$value = "myTextValue";
$key = 'c40f5b7ad3b7c787d400e923e461064b141fa878ce61cb0d1782593a5a2d842832c80fc2';

$enc = @encrypt_openssl($value, $key);
//$enc = @encrypt_mcrypt($value, $key);
$original_openssl = @decrypt_openssl($enc, $key);
$original_mcrypt = @decrypt_mcrypt($enc, $key);

echo $original_mcrypt."\n";
echo $original_openssl."\n";

function encrypt_openssl($string, $key) {
    $iv_size = openssl_cipher_iv_length("BF-CBC");
    $iv = openssl_random_pseudo_bytes($iv_size);
    $enc = openssl_encrypt($string, "BF-CBC", pack('H*',$key), OPENSSL_RAW_DATA, $iv);
    return base64_encode($iv.$enc);
}

function encrypt_mcrypt($string, $key) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $enc = mcrypt_encrypt(MCRYPT_BLOWFISH, pack('H*', $key), $string, MCRYPT_MODE_CBC, $iv);
    return base64_encode($iv.$enc);
}

function decrypt_openssl($enc, $key) {
    $iv_size = openssl_cipher_iv_length("BF-CBC");
    $dec = base64_decode($enc);
    $iv = substr($dec, 0, $iv_size);
    $string = openssl_decrypt(substr($dec, $iv_size), "BF-CBC", pack('H*',$key), OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING | OPENSSL_DONT_ZERO_PAD_KEY, $iv);
    return rtrim($string, "\x00");
}

function decrypt_mcrypt($enc, $key) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
    $dec = base64_decode($enc);
    $iv = substr($dec, 0, $iv_size);
    $string = mcrypt_decrypt(MCRYPT_BLOWFISH, pack('H*', $key), substr($dec, $iv_size), MCRYPT_MODE_CBC, $iv);
    return rtrim($string, "\x00");
}

When encrypting with openssl there is some extra binary data being added.

Not an encryption guru and only get half of it, this was as far as I got with the help of other stackoverflow posts and almighty google


EDIT

Following Topaco advice I came to the following code that now works:

function encrypt_openssl($string, $key) {
    $string_padded = $string;
    if (strlen($string_padded) % 8) {
        $string_padded = str_pad($string_padded,
            strlen($string_padded) + 8 - strlen($string_padded) % 8, "\0");
    }
    $iv_size = openssl_cipher_iv_length("BF-CBC");
    $iv = openssl_random_pseudo_bytes($iv_size);
    $enc = openssl_encrypt($string_padded, "BF-CBC", pack('H*',$key), OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $iv);
    return base64_encode($iv.$enc);
}

Solution

  • openssl_encrypt/decrypt uses PKCS7-padding by default, mcrypt_encrypt/decrypt uses Zero-Byte-padding.

    The observed extra data after decryption are the padding-bytes of the PKCS7-padding: The current code uses PKCS7-padding for the openssl_encrypt-call (OPENSSL_ZERO_PADDING-flag not set). Since no padding is used for the openssl_decrypt-call (OPENSSL_ZERO_PADDING-flag set), the padding is still present after decryption. Note, the OPENSSL_ZERO_PADDING-flag disables the padding, it does not mean Zero-Byte-padding.

    Although PKCS7-padding is generally the better choice compared to Zero-Byte-padding (because the latter is unreliable), in this case it makes more sense to use the padding of the old data, i.e. Zero-Byte-padding, with regard to the compatibility of the old data. Otherwise there would be data with different padding, which generally cannot be derived from the data, e.g. the padded final block 41 42 43 44 45 46 02 02 may have been created by PKCS7-padding or by Zero-Byte-padding:

    before padding             after padding
    41 42 43 44 45 46 __ __ -> 41 42 43 44 45 46 02 02  PKCS7-Padding
    41 42 43 44 45 46 02 02 -> 41 42 43 44 45 46 02 02  Zero-Byte-Padding (variant of mcrypt_encrypt)  
    

    This would make the un-padding more complex. Using Zero-Byte-padding avoids this problem.

    Since openssl_encrypt/decrypt does not support Zero-Byte-padding, it must be explicitly implemented. It makes sense to use the Zero-Byte-padding-variant of mcrypt_encrypt: If the plaintext is already divisible by the blocksize (8 Byte for Blowfish) no additional block of zero bytes is added. Otherwise, padding is done with zero bytes until the length of the plaintext corresponds to an integer multiple of the blocksize.

    The Zero-Byte-padding must have taken place before the openssl_encrypt-call. In addition, the padding in the openssl_encrypt-call itself has to be disabled (set OPENSSL_ZERO_PADDING-flag, analogous to the openssl_decrypt-call).