What are the alternatives for mcrypt_* functions?

Asked

Viewed 215 times

9

The functions of the type mcrypt_* were discontinued in PHP 7.1.0 and removed in PHP 7.2.0 as shown in manual:

Warning This Function was DEPRECATED in PHP 7.1.0, and REMOVED in PHP 7.2.0.

I was looking at this question Migrate PHP function from mcrypt to Openssl but there is only one alternative for migration. What other alternatives are there?

To give a better context I have some very old functions to encrypt strings, such as some strings that go through GET with information that I don’t want the client to see.

That is, instead of displaying the id (or any other string)

meusite.pt/user/Edit/&id=354

Displays an encryption so the client does not invent:

meusite.en/user/Edit/&id=5x6ypIpvj6Wd9TpekI2s4DetXUgBTSpZ_cyEAJxxBVU

The functions I currently use are these:

public function encode( $text )
{
    $myKey = "stackoverflowemportugues";

    if( !$text )
        return false;

    $iv_size   = mcrypt_get_iv_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB );
    $iv        = mcrypt_create_iv( $iv_size, MCRYPT_RAND );
    $crypttext = mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $myKey,
                                $text, MCRYPT_MODE_ECB, $iv );

    return trim( base64_encode( $crypttext ) );
}

public function decode( $text )
{
    $myKey = "stackoverflowemportugues";

    if( !$text )
        return false;

    $crypttext   = base64_decode( $text );
    $iv_size     = mcrypt_get_iv_size( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB );
    $iv          = mcrypt_create_iv( $iv_size, MCRYPT_RAND );
    $decrypttext = mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $myKey,
                                  $crypttext, MCRYPT_MODE_ECB, $iv );

    return trim( $decrypttext );
}

PS: By the way, if you want to improve the answer, you can migrate the functions to be more complete.

  • I believe that it is better than direct conversion to analyze what is best for each specific use.

  • 1

    A string based on 64 should not contain a text, right? So why trim?

  • 1

    @Costamilam yes, but that’s not very important to the question.

  • 1

    You can use: password_hash.

  • @Ivanferrer I didn’t mention the functions of type password_* because it makes no sense in this case, I already use them when I move with passwords.

2 answers

1

First there are some problems in the original code, such as the use of ECB mode. Second, it depends on the version you are using, I am considering that you are using PHP 7.2.


Unfortunately, there is no compatible alternative, not "natively". That’s because you’re using MCRYPT_RIJNDAEL_256. AES, which is the standardization of Rijndael, uses a 128-bit block in all modes (AES-128, AES-192 and AES-256). Meanwhile, the MCRYPT_RIJNDAEL_256 uses also 256 bit blocks, so it is incompatible.

Port of MCRYPT for OpenSSL is possible in a single case. If you are using the MCRYPT_RIJNDAEL_128, you can see this in practice here. So if you are using this algorithm you can migrate to Openssl using AES-128, which will be equivalent.

None of the current Apis (such as Openssl and Sodium) expose Rijndael directly, at least as far as I know. So to migrate you would have to decipher all the texts and migrate to another algorithm and API.


There are two alternatives (considering private-key/symmetric encryption):

  • Openssl (you will use use AES or Camellia)
  • Libsodium (you will use Salsa20, Xsalsa20, Chacha20, Xchacha20 or AES)

Libsodium uses "more performative" algorithms (ignoring AES-NI, including it only works with AES if the CPU such instruction!) and has preference for encrypted streams and authenticated encryptions (using Poly1035, or GCM for AES). My response may be biased, as I severely prefer Sodium to archaic Openssl...


Whereas your goals are:

  • Encrypt a message using a symmetric key (both people know the same key) and prevent people who don’t know the key from reading the content of the message.

  • Authenticate the message using a symmetric key (both people know the same key) and prevent people without such a key from being able to alter the ciphertext. Anyway, guaranteed that the message was not altered by someone who does not have the key.

The Libsodium, native in PHP 7.2, provides a much easier-to-use API, and you can use it as follows:

public function encode($text) {
    $key = "stackoverflowemportugues";  // Sua chave-secreta que tem que ter 256 bits, gere uma usando o random_bytes!
    $nonce = random_bytes(SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES);

    return $nonce . sodium_crypto_aead_chacha20poly1305_ietf_encrypt($text , "", $nonce , $key);
}


public function decode($text) {
    $key = "stackoverflowemportugues"; // Sua chave-secreta que tem que ter 256 bits, gere uma usando o random_bytes!
    $nonce = mb_substr($text, 0, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, '8bit');

    $decoded = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(mb_substr($text, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, mb_strlen($text, '8bit'), '8bit'), "", $nonce, $key);
    if ($decoded === false) {
         throw new Exception("A autenticação deu errado, provavelmente a mensagem foi alterada!");
    }

    return $decoded;
}

I still need to test, but I no longer have PHP installed. :\

The result may need to be encoded for some user-friendly format, such as Base64, Hex (...), depending on your use case. I currently believe that the result of encode shall be in torque and the input of the decode is also supposed to be binary.

This example above will use the 96-bit Chacha20 variant of nonce, thus avoiding nonce collision. The original (sodium_crypto_aead_chacha20poly1305_encrypt, without the _ietf_) has only 64 bits. This construction uses encrypt-then-mac, so if the message, if modified, will not be read, nor partially.

This is equivalent to using AES-GCM.

-2

Here is a solution for what you need:

public function encode($encrypt, $key, $iv) {
    $encrypted = openssl_encrypt($encrypt, 'aes-256-cbc', $key, 0, $iv);
    return base64_encode($encrypted . '::' . $iv);
}

public function decode($decrypt, $key) {
    list($encrypted_data, $iv) = explode('::', base64_decode($decrypt), 2);
    return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
}

Reference here.

But I suggest making a future implementation to use password_hash:

function password_hash($password, $algo, array $options = array()) {
        if (!function_exists('crypt')) {
            trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
            return null;
        }
        if (!is_string($password)) {
            trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
            return null;
        }
        if (!is_int($algo)) {
            trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
            return null;
        }
        switch ($algo) {
            case PASSWORD_BCRYPT:
                // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
                $cost = 10;
                if (isset($options['cost'])) {
                    $cost = $options['cost'];
                    if ($cost < 4 || $cost > 31) {
                        trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
                        return null;
                    }
                }
                $required_salt_len = 22;
                $hash_format = sprintf("%s%02d$", PASSWORD_PREFIX, $cost);
                break;
            default:
                trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
                return null;
        }
        if (isset($options['salt'])) {
            switch (gettype($options['salt'])) {
                case 'NULL':
                case 'boolean':
                case 'integer':
                case 'double':
                case 'string':
                    $salt = (string) $options['salt'];
                    break;
                case 'object':
                    if (method_exists($options['salt'], '__tostring')) {
                        $salt = (string) $options['salt'];
                        break;
                    }
                case 'array':
                case 'resource':
                default:
                    trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
                    return null;
            }
            if (strlen($salt) < $required_salt_len) {
                trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
                return null;
            } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
                $salt = str_replace('+', '.', base64_encode($salt));
            }
        } else {
            $buffer = '';
            $raw_length = (int) ($required_salt_len * 3 / 4 + 1);
            $buffer_valid = false;
            if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
                $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
                if ($buffer) {
                    $buffer_valid = true;
                }
            }
            if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
                $buffer = openssl_random_pseudo_bytes($raw_length);
                if ($buffer) {
                    $buffer_valid = true;
                }
            }
            if (!$buffer_valid && is_readable('/dev/urandom')) {
                $f = fopen('/dev/urandom', 'r');
                $read = strlen($buffer);
                while ($read < $raw_length) {
                    $buffer .= fread($f, $raw_length - $read);
                    $read = strlen($buffer);
                }
                fclose($f);
                if ($read >= $raw_length) {
                    $buffer_valid = true;
                }
            }
            if (!$buffer_valid || strlen($buffer) < $raw_length) {
                $bl = strlen($buffer);
                for ($i = 0; $i < $raw_length; $i++) {
                    if ($i < $bl) {
                        $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
                    } else {
                        $buffer .= chr(mt_rand(0, 255));
                    }
                }
            }
            $salt = str_replace('+', '.', base64_encode($buffer));
        }
        $salt = substr($salt, 0, $required_salt_len);
        $hash = $hash_format . $salt;
        $ret = crypt($password, $hash);
        if (!is_string($ret) || strlen($ret) <= 13) {
            return false;
        }
        return $ret;
    }
  • I referenced an answer that uses openssl_encrypt. Isn’t that too complex for the given example? And could you explain better why you are implementing it? Wasn’t it simpler to use an alternative function? Are there more options?! I use type functions password_ for passwords. But I don’t think it’s justified in this case.

  • In fact, I’m converting your deprecated function into an unadjusted method, to suit your purpose, if you use pre-existing passwords from your users, the ideal is to use a newer php method, which is password_hash, is much safer, see on php documentation how to use.

  • 1

    Ivan, I had already mentioned openssl_encrypt in the question, this solution has already been answered in another question, what I want are alternatives to that, if any. A password_hash makes no sense in this case. I am not encrypting passwords.

  • 3

    The password_hash has nothing to do with the subject, they are completely different purposes.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.