Cake 3.8: Decrypt data which was encrypted with Security::cipher() of 2.3

I’m upgrading CakePHP from 2.3 to 3.8 for an application. Some data is encrypted with Security:: cipher() and stored in the database. I want to decrypt the data in 3.8 and use it. However, since the cipher method has been removed from the library, I don’t know how to decrypt it.

For 2.3 the data gets encrypted/decrypted like below.
Encryption

bin2hex(Security::cipher('data', Configure::read('Security.cipherSeed')));

Decryption

Security::cipher(pack("H*", 'encrypted data'), Configure::read('Security.cipherSeed'))

I have tried to use the cipher method itself copied from 2.3 Security component like below to see if it works but it still doesn’t.

public static function cipher($text, $key) {
    if (empty($key)) {
        trigger_error(__d('cake_dev', 'You cannot use an empty key for %s', 'Security::cipher()'), E_USER_WARNING);
       return '';
     }
     srand((int)(float)Configure::read('Security.cipherSeed'));
     $out = '';
     $keyLength = strlen($key);
     for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
         $j = ord(substr($key, $i % $keyLength, 1));
         while ($j--) {
             rand(0, 255);
         }
         $mask = rand(0, 255);
         $out .= chr(ord(substr($text, $i, 1)) ^ $mask);
     }
     srand();
     return $out;
 }

Is Configure::read('Security.cipherSeed') returning the same value in both versions?

Yes, they both return the same cipherSeed.

I just tried this code with a key I made up, and it works just fine to recover the original text. It seems weird that the cipher seed is used as both the key and random seed, is that definitely how it’s set up?

Oh, are your two versions running on the same version of PHP? The documentation indicates that “As of PHP 7.1.0, rand() uses the same random number generator as mt_rand()”, so if your old version is on something less than 7.1 and the new version is on 7.1 or higher, that’ll break it for sure.

And there’s subsequently been a bug fix in 7.2 that changes the sequence that mt_rand generates, so if your old version is on 7.1 and new version is on 7.2 or later, then that’ll also break it.

I just tried this code with a key I made up, and it works just fine to recover the original text. It seems weird that the cipher seed is used as both the key and random seed, is that definitely how it’s set up?

It is how it’s set up. Please refer to the code. File Cake/Utility/Security.php | CakePHP 2.3

Oh, are your two versions running on the same version of PHP? The documentation indicates that “As of PHP 7.1.0, rand() uses the same random number generator as mt_rand()”, so if your old version is on something less than 7.1 and the new version is on 7.1 or higher, that’ll break it for sure.

The old version of my app is running on PHP 5.6 and the one is on PHP 7.4, so that sounds like the reason. I just tried again after replacing rand() and srand() with mt_rand() and mt_srand() but not working.

There is nothing at all that you can do in PHP7.1 or later to get the same sequence as rand provided in 5.6. (Well, you might be able to find the code for that original version and re-implement it somehow, but that’s very non-trivial.) This cipher algorithm seems weak anyway, is there a compelling reason not to use actual encryption? You could add a data migration step that runs in the Cake2.3 code under PHP5.6 that de-ciphers it and encrypts it instead, using an algorithm that’s still supported in the latest Cake. Unlike the cipher thing, encryption is not affected by changes in random number generator sequences.

If that’s not an option, then I think you’ll need to de-cipher it under 2.3, then re-cipher it with 4.x.

I was actually going to decipher the data and hash it on the newer version since I’ve read the cipher method is pretty weak. Now that I know I can’t decipher it on the newer version, I guess decipher and hash it on the old version before upgrading. Thank you for the help!

When you say hash, you mean encrypt, right? Because hashes are one-way and you won’t be able to get the original data back. Unless these are passwords, in which case change to hashes ASAP!