1

I'm upgrading a legacy application from PHP 7.0 to 7.2 and my decrypt function isn't working when I switch out mcrypt for openssl.

I tried the existing answers like mcrypt is deprecated, what is the alternative?, and Gists like https://gist.github.com/odan/c1dc2798ef9cedb9fedd09cdfe6e8e76, but I'm still not able to make the code work.

Can anyone shed some light what I'm doing wrong?

Old code

function decrypt($value, $key) {
    $ivLength = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $iv = substr($value, 0, $ivLength);
    return rtrim(
        mcrypt_decrypt(
            MCRYPT_RIJNDAEL_128,
            hash('sha256', $key, true),
            substr($value, $ivLength),
            MCRYPT_MODE_CBC,
            $iv
        ),
        "\0"
    );
}

New code (not working with existing inputs)

function decrypt($value, $key) {
    $ivLength = openssl_cipher_iv_length('AES-128-CBC');
    $iv = substr($value, 0, $ivLength);
    // Note: $key is hashed because it was hashed in the old encrypt function below
    return openssl_decrypt(substr($value, $ivLength), 'AES-128-CBC', hash('sha256', $key, true), OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
}

For context, here's how the old code encrypted the values:

function encrypt($value, $key) {
    $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);
    return $iv . mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128,
            hash('sha256', $key, true),
            $value,
            MCRYPT_MODE_CBC,
            $iv
        );
}

Also, the raw value in $value is twice as long when output to the error log in the new code than the old code and looks similar but with more characters.

Anton
  • 3,998
  • 25
  • 40

1 Answers1

4

The main issue here is the key size. You're creating the key with SHA256 which returns a 256 bit hash, therefore you're using AES / Rijndael with a 256 bit key.

The number in Rijndael-128 defines the block size, the key size is determined by the length of the key we use. The number in AES-128 defines the key size (the block size is constant, 128 bits), and if the actual key length is different than this number, then the key is shortened or expanded (with zero bytes) to fit the selected key size.

This means that your mcrypt code uses Rijndael-128 (AES) with a 256 bit key, in CBC mode. The openssl equivalent is AES-256-CBC, and if we use this algorithm then mcrypt and openssl should produce compatible results.

function decrypt_mcrypt_with_openssl($value, $key) {
    $iv = substr($value, 0, 16);
    $ciphertext = substr($value, 16);
    $key = hash('sha256', $key, true);
    $plaintext = openssl_decrypt(
        $ciphertext, 'AES-256-CBC', $key, 
        OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, 
        $iv
    );
    return rtrim($plaintext, "\0");
}

SHA256 is not suitable as a key derivation function. If you're deriving your key from a password you can use hash_pbkdf2 with a random salt and a high enough number of iterations. Or you could create a cryptographically secure pseudo-random key with openssl_random_pseudo_bytes.

I think it's best to stop using mcrypt completely and use only openssl. Mcrypt doesn't support PKCS7 padding, it doesn't provide any authenticated encryption algorithms, and it is not being maintained anymore.

t.m.adam
  • 15,106
  • 3
  • 32
  • 52
  • You are the best ever coder I've come across so far @t.m.adam. Yes, it worked out right. Wish I had you as my teacher. Thanks. – MITHU Feb 22 '20 at 14:28