'Exact alternate to mcrypt_encrypt in PHP 7.2

Since mcrypt_encrypt is no longer supported in PHP 7.2, I am trying for exact alternate to this function.

After reading many SO answers I have found the following code which uses PHPSECLIB, but it's not producing the exact encrypted text as mcrypt.

function encryptRJ256($key,$iv,$string_to_encrypt)
    {

       // $rtn = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $string_to_encrypt, MCRYPT_MODE_CBC, $iv);

      $rijndael = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CBC);  
      $rijndael->setKey($key);
      $rijndael->setIV($iv);
      $rijndael->setKeyLength(256);
      $rijndael->disablePadding();
      $rijndael->setBlockLength(256);
      $rtn = $rijndael->encrypt($string_to_encrypt);    
      $rtn = base64_encode($rtn);
      return($rtn);
    }

My Key and IV is

  $ky = 'lkirwf897+22#bbtrm8814z5qq=498j5'; 

  $iv = '741952hheeyy66#cs!9hjv887mxx7@8y';

The first 42 chars are equal but the rest is different as you can see

Text to encrypt: 57F0-ECD3-1A3B-341E-BA39-F81B-F020-0DE0

mcrypt_encrypt output:
3uw7mVZthiIPPNosvppZHd1jEau3Ul+0BQ4AVS2t80skauq3Zv9z5uztvmiBpYqQcKGIDP5YHfdEBhPBfdVbxg==

phpseclib output:
3uw7mVZthiIPPNosvppZHd1jEau3Ul+0BQ4AVS2t80tKnjjxVhuAwh3E1S5OnH1up5AujvQu1Grgyv16tNIEDw==

I need to produce the same encrypted text because this text is decrypted by another program which I can't change.

So my question is, is it possible to produce the same encrypted text as mcrypt_encrypt using phpseclib or any other way?



Solution 1:[1]

You might need to "pad all the things"

If you read this entry on leaseweb it states over and over that mcrypt pads thing automatically to the correct block sizes that it can chew. That leads of course to different outputs because of all the null bytes.

So I'd suggest you make sure all your data you feed into your phpseclib code are padded with the correct amount of null bytes to simulate the input mcrypt feeds into the cipher.

Default if you look at the code of PHPSECLIB it pads by a character decided by the number of characters to pad.
mcrypt however pads with character 0.

So, let's fix this.

The changed code you need is:

    $cipher->disablePadding();
    $length = strlen($text);
    $pad = 32 - ($length % 32);
    $text = str_pad($text, $length + $pad, chr(0));

I used the latest version of PHPSECLIB as avaialble on github, so my code differs from your example code. This is the full working example on my machine.

<?php 
include "vendor/autoload.php";
include "phpseclib/bootstrap.php";
set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');
include('Crypt/Rijndael.php');
include('Crypt/Random.php');
use phpseclib\Crypt\Rijndael as Crypt_Rijndael;

$text = '57F0-ECD3-1A3B-341E-BA39-F81B-F020-0DE0';

$secret = 'lkirwf897+22#bbtrm8814z5qq=498j5';

$iv = '741952hheeyy66#cs!9hjv887mxx7@8y';

function encrypt128($secret, $iv, $str)
{ 

    return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $secret, $str, MCRYPT_MODE_CBC, $iv));
}

function encryptRJ256($key,$iv,$text)
{
    $cipher = new Crypt_Rijndael('cbc'); 
    $cipher->setBlockLength(256);
    // keys are null-padded to the closest valid size
    // longer than the longest key and it's truncated
    $cipher->setKeyLength(256);
    $cipher->setKey($key);
    // the IV defaults to all-NULLs if not explicitly defined
    $cipher->setIV($iv);
    $cipher->disablePadding();
    $length = strlen($text);
    $pad = 32 - ($length % 32);
    $text = str_pad($text, $length + $pad, chr(0));
    return base64_encode($cipher->encrypt($text));
}
function decryptRJ256($key,$iv,$text)
{
    $cipher = new Crypt_Rijndael('cbc'); // could use CRYPT_RIJNDAEL_MODE_CBC
    $cipher->setBlockLength(256);
    // keys are null-padded to the closest valid size
    // longer than the longest key and it's truncated
    $cipher->setKeyLength(256);
    $cipher->setKey($key);
    // the IV defaults to all-NULLs if not explicitly defined
    $cipher->setIV($iv);
    $cipher->disablePadding();
    return $cipher->decrypt(base64_decode($text)); 
}   

echo $text;
echo encrypt128($secret, $iv, $text);
echo "\n";
$text = encryptRJ256($secret, $iv, $text);
echo $text;
echo "\n";
echo decryptRJ256($secret, $iv, $text);

showing output

Solution 2:[2]

As it technically works encrypting and decrypting with this method, the actual encrypted string may be different depending on the input. If the input string is exactly 32 characters long, the encrypted string will be much longer as it would be with the original mcrypt method.

The reason is the calculation of the $pad variable. With a 32 char input $pad will be 32, which results in a 64 char $text variable. If you use a rtrim in the decrypting method its not actually a problem.

To avoid this, i found another way to pad the input string in the mcrypt_compat package from phpseclib:

https://github.com/phpseclib/mcrypt_compat/blob/e38b76f02e6cf97aca05f5738eee1b917d922101/lib/mcrypt.php#L721 :

$extra = strlen($data) % 32;
if ($extra) {
    $data.= str_repeat("\0", 32 - $extra);
}

With this a 32 char input string will not be padded at all. This results in the exact same encrypted string like with mcrypt directly.

Hopefully this helps someone - or use the mcrypt_compat package directly.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Alex