Mcrypt устарела, какова альтернатива?

Расширение mcrypt устарело и будет удалено в PHP 7.2 в соответствии с комментарием, размещенным здесь. Поэтому я ищу альтернативный способ шифрования паролей.

Прямо сейчас я использую что-то вроде

mcrypt_encrypt(MCRYPT_RIJNDAEL_128, md5($key, true), $string, MCRYPT_MODE_CBC, $iv)

Мне нужно ваше мнение для лучшего/самого надежного способа шифрования паролей, зашифрованный пароль, конечно, должен поддерживаться PHP 7.xx и также должен быть дешифруемым, потому что мои клиенты хотят иметь возможность "восстанавливать" свои пароли без генерации нового один.

Ответ 1

Лучше всего использовать хэш-пароли, чтобы они не были дешифруемыми. Это делает вещи немного сложнее для злоумышленников, которые могли получить доступ к вашей базе данных или файлам.

Если вы должны зашифровать свои данные и сделать их дешифруемыми, руководство по защищенному шифрованию/расшифровке доступно по адресу https://paragonie.com/white-paper/2015-secure-php-data-encryption. Подводя итог этой ссылке:

  • Используйте Libsodium - расширение PHP
  • Если вы не можете использовать Libsodium, используйте defuse/php-encryption - Прямой PHP-код
  • Если вы не можете использовать Libsodium или defuse/php-encryption, используйте OpenSSL - на многих серверах уже установлено это, Если нет, его можно скомпилировать с помощью --with-openssl [= DIR]

Ответ 2

Как предлагает @rqLizard, вы можете вместо этого использовать PHP-функции openssl_encrypt/ openssl_decrypt которые предоставляют гораздо лучшую альтернативу для реализации AES (Advanced Encryption Standard), также известного как шифрование Rijndael.

Согласно следующему комментарию Скотта на php.net:

Если вы пишете код для шифрования/шифрования данных в 2015 году, вы должны использовать openssl_encrypt() и openssl_decrypt(). Базовая библиотека (libmcrypt) была заброшена с 2007 года и работает намного хуже, чем OpenSSL (которая использует AES-NI на современных процессорах и безопасна для тайминга).

Кроме того, MCRYPT_RIJNDAEL_256 - это не AES-256, это другой вариант блочного шифра Rijndael. Если вы хотите AES-256 в mcrypt, вы должны использовать MCRYPT_RIJNDAEL_128 с 32-байтовым ключом. OpenSSL делает более очевидным, какой режим вы используете (т.е. aes-128-cbc против aes-256-ctr).

OpenSSL также использует заполнение PKCS7 с режимом CBC, а не заполнение байтов mcrypt NULL. Таким образом, mcrypt с большей вероятностью сделает ваш код уязвимым для атак оракула, чем OpenSSL.

Наконец, если вы не аутентифицируете свои шифротексты (Encrypt Then MAC), вы делаете это неправильно.

Дальнейшее чтение:

Примеры кода

Пример № 1

Пример AES Authenticated Encryption в режиме GCM для PHP 7. 1+

<?php
//$key should have been previously generated in a cryptographically safe way, like openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$cipher = "aes-128-gcm";
if (in_array($cipher, openssl_get_cipher_methods()))
{
    $ivlen = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivlen);
    $ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv, $tag);
    //store $cipher, $iv, and $tag for decryption later
    $original_plaintext = openssl_decrypt($ciphertext, $cipher, $key, $options=0, $iv, $tag);
    echo $original_plaintext."\n";
}
?>

Пример № 2

Пример AES Authenticated Encryption для PHP 5. 6+

<?php
//$key previously generated safely, ie: openssl_random_pseudo_bytes
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );

//decrypt later....
$c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac))//PHP 5.6+ timing attack safe comparison
{
    echo $original_plaintext."\n";
}
?>

Пример № 3

На основе приведенных выше примеров я изменил следующий код, предназначенный для шифрования идентификатора сеанса пользователя:

class Session {

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $session_id, MCRYPT_MODE_CBC, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($encrypt);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId);
    // Decrypt the string.
    $decryptedSessionId = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $decoded, MCRYPT_MODE_CBC, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, "\0");
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    return md5($this->_getSalt());
  }

  public function _getSalt() {
    return md5($this->drupal->drupalGetHashSalt());
  }

}

в:

class Session {

  const SESS_CIPHER = 'aes-128-cbc';

  /**
   * Encrypts the session ID and returns it as a base 64 encoded string.
   *
   * @param $session_id
   * @return string
   */
  public function encrypt($session_id) {
    // Get the MD5 hash salt as a key.
    $key = $this->_getSalt();
    // For an easy iv, MD5 the salt again.
    $iv = $this->_getIv();
    // Encrypt the session ID.
    $ciphertext = openssl_encrypt($session_id, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Base 64 encode the encrypted session ID.
    $encryptedSessionId = base64_encode($ciphertext);
    // Return it.
    return $encryptedSessionId;
  }

  /**
   * Decrypts a base 64 encoded encrypted session ID back to its original form.
   *
   * @param $encryptedSessionId
   * @return string
   */
  public function decrypt($encryptedSessionId) {
    // Get the Drupal hash salt as a key.
    $key = $this->_getSalt();
    // Get the iv.
    $iv = $this->_getIv();
    // Decode the encrypted session ID from base 64.
    $decoded = base64_decode($encryptedSessionId, TRUE);
    // Decrypt the string.
    $decryptedSessionId = openssl_decrypt($decoded, self::SESS_CIPHER, $key, $options=OPENSSL_RAW_DATA, $iv);
    // Trim the whitespace from the end.
    $session_id = rtrim($decryptedSessionId, '\0');
    // Return it.
    return $session_id;
  }

  public function _getIv() {
    $ivlen = openssl_cipher_iv_length(self::SESS_CIPHER);
    return substr(md5($this->_getSalt()), 0, $ivlen);
  }

  public function _getSalt() {
    return $this->drupal->drupalGetHashSalt();
  }

}

Для пояснения, вышеупомянутое изменение не является истинным преобразованием, так как в обоих шифровании используется другой размер блока и разные зашифрованные данные. Кроме того, заполнение по умолчанию отличается, MCRYPT_RIJNDAEL поддерживает только нестандартное заполнение нулями. @zaph


Дополнительные примечания (из комментариев @zaph):

  • Rijndael 128 (MCRYPT_RIJNDAEL_128) эквивалентна AES, однако Rijndael 256 (MCRYPT_RIJNDAEL_256) не является AES-256, поскольку 256 определяет размер блока 256 бит, в то время как AES имеет только один размер блока: 128 бит. Таким образом, Rijndael с размером блока в 256 бит (MCRYPT_RIJNDAEL_256) был ошибочно назван из-за выбора, сделанного разработчиками mcrypt. @zaph
  • Rijndael с размером блока 256 может быть менее безопасным, чем с размером блока 128 бит, потому что последний имел гораздо больше обзоров и использований. Во-вторых, совместимость затрудняется тем, что, в то время как AES обычно доступен, а Rijndael с размером блока в 256 битов - нет.
  • Шифрование с различными размерами блоков для Rijndael производит разные зашифрованные данные.

    Например, MCRYPT_RIJNDAEL_256 (не эквивалентный AES-256) определяет другой вариант блочного шифра Rijndael с размером 256 бит и размером ключа на основе переданного ключа, где aes-256-cbc - это Rijndael с размером блока 128 бит с размером ключа 256 бит. Поэтому они используют блоки разных размеров, что приводит к совершенно разным зашифрованным данным, так как mcrypt использует число для указания размера блока, где OpenSSL использовал число для указания размера ключа (AES имеет только один размер блока 128 бит). Таким образом, в основном AES - это Rijndael с размером блока 128 бит и ключами 128, 192 и 256 бит. Поэтому лучше использовать AES, который называется Rijndael 128 в OpenSSL.

Ответ 3

Реализация Rijndael на чистом PHP существует с phpseclib, доступным как пакет composer, и работает на PHP 7.3 (проверено мной).

В документации phpseclib есть страница, на которой генерируется пример кода после ввода основных переменных (шифр, режим, размер ключа, размер бита). Для Rijndael выводится следующее: ECB, 256, 256:

код с mycrypt

$decoded = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ENCRYPT_KEY, $term, MCRYPT_MODE_ECB);

работает так с библиотекой

$rijndael = new \phpseclib\Crypt\Rijndael(\phpseclib\Crypt\Rijndael::MODE_ECB);
$rijndael->setKey(ENCRYPT_KEY);
$rijndael->setKeyLength(256);
$rijndael->disablePadding();
$rijndael->setBlockLength(256);

$decoded = $rijndael->decrypt($term);

* $term был base64_decoded

Ответ 4

Вы можете использовать phpseclib пакет pollyfill. Вы не можете использовать open ssl или libsodium для шифрования/дешифрования с помощью rijndael 256. Еще одна проблема, вам не нужно заменять какой-либо код.

Ответ 5

Вы должны использовать OpenSSL поверх mcrypt как он активно развивается и поддерживается. Это обеспечивает лучшую безопасность, ремонтопригодность и портативность. Во-вторых, он выполняет шифрование/дешифрование AES намного быстрее. По умолчанию используется заполнение PKCS7, но вы можете указать OPENSSL_ZERO_PADDING если вам это нужно. Для использования с 32-байтовым двоичным ключом вы можете указать aes-256-cbc что гораздо более очевидно, чем MCRYPT_RIJNDAEL_128.

Вот пример кода с использованием Mcrypt:

Неаутентифицированная библиотека шифрования AES-256-CBC, написанная в Mcrypt с дополнением PKCS7.

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeMcryptAES
{
    const CIPHER = MCRYPT_RIJNDAEL_128;

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mcrypt_create_iv($ivsize, MCRYPT_DEV_URANDOM);

        // Add PKCS7 Padding
        $block = mcrypt_get_block_size(self::CIPHER);
        $pad = $block - (mb_strlen($message, '8bit') % $block, '8bit');
        $message .= str_repeat(chr($pad), $pad);

        $ciphertext = mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $message,
            MCRYPT_MODE_CBC,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = mcrypt_get_iv_size(self::CIPHER);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        $plaintext = mcrypt_decrypt(
            MCRYPT_RIJNDAEL_128,
            $key,
            $ciphertext,
            MCRYPT_MODE_CBC,
            $iv
        );

        $len = mb_strlen($plaintext, '8bit');
        $pad = ord($plaintext[$len - 1]);
        if ($pad <= 0 || $pad > $block) {
            // Padding error!
            return false;
        }
        return mb_substr($plaintext, 0, $len - $pad, '8bit');
    }
}

А вот версия, написанная с использованием OpenSSL:

/**
 * This library is unsafe because it does not MAC after encrypting
 */
class UnsafeOpensslAES
{
    const METHOD = 'aes-256-cbc';

    public static function encrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = openssl_random_pseudo_bytes($ivsize);

        $ciphertext = openssl_encrypt(
            $message,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        return $iv . $ciphertext;
    }

    public static function decrypt($message, $key)
    {
        if (mb_strlen($key, '8bit') !== 32) {
            throw new Exception("Needs a 256-bit key!");
        }
        $ivsize = openssl_cipher_iv_length(self::METHOD);
        $iv = mb_substr($message, 0, $ivsize, '8bit');
        $ciphertext = mb_substr($message, $ivsize, null, '8bit');

        return openssl_decrypt(
            $ciphertext,
            self::METHOD,
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );
    }
}

Источник: Если вы вводите слово MCRYPT в свой PHP-код, вы делаете это неправильно.

Ответ 6

Как подробно описано в других ответах здесь, лучшее решение, которое я нашел, - это использование OpenSSL. Он встроен в PHP и вам не нужна никакая внешняя библиотека. Вот простые примеры:

Для шифрования:

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

Для расшифровки:

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

Ссылочная ссылка: https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/

Ответ 7

Как уже указывалось, вы не должны хранить пароли своих пользователей в формате, который может быть дешифруемым. Реверсивное шифрование обеспечивает легкий путь для хакеров, чтобы узнать пароли ваших пользователей, которые распространяются на то, чтобы подвергать учетные записи ваших пользователей другим сайтам, находящимся под угрозой, если они используют там один и тот же пароль.

PHP предоставляет пару мощных функций для случайного, одностороннего хэш-шифрования — password_hash() и password_verify(). Поскольку хеш автоматически солености, хакеры не могут использовать предварительно скомпилированные таблицы хэшей паролей, чтобы перепроектировать пароль. Установите опцию PASSWORD_DEFAULT, и будущие версии PHP автоматически будут использовать более сильные алгоритмы для генерации хэшей паролей без необходимости обновления кода.

Ответ 9

Я смог перевести мой объект Crypto

  • Получите копию php с mcrypt для расшифровки старых данных. Я зашел на http://php.net/get/php-7.1.12.tar.gz/from/a/mirror, скомпилировал его, затем добавил расширение ext/mcrypt (configure; make; make install). Я думаю, что мне пришлось добавить строку extenstion = mcrypt.so в php.ini. Серия скриптов для создания промежуточных версий данных со всеми данными в незашифрованном виде.

  • Создайте открытый и закрытый ключ для openssl

    openssl genrsa -des3 -out pkey.pem 2048
    (set a password)
    openssl rsa -in pkey.pem -out pkey-pub.pem -outform PEM -pubout
    
  • Для шифрования (используя открытый ключ) используйте openssl_seal. Из того, что я прочитал, openssl_encrypt, использующий ключ RSA, ограничен на 11 байтов меньше длины ключа (см. Http://php.net/manual/en/function.openssl-public-encrypt.php комментарий Томаса Хорстена)

    $pubKey = openssl_get_publickey(file_get_contents('./pkey-pub.pem'));
    openssl_seal($pwd, $sealed, $ekeys, [ $pubKey ]);
    $encryptedPassword = base64_encode($sealed);
    $key = base64_encode($ekeys[0]);
    

Вы могли бы вероятно хранить сырой двоичный файл.

  • Расшифровать (используя закрытый ключ)

    $passphrase="passphrase here";
    $privKey = openssl_get_privatekey(file_get_contents('./pkey.pem'), $passphrase);
    // I base64_decode() from my db columns
    openssl_open($encryptedPassword, $plain, $key, $privKey);
    echo "<h3>Password=$plain</h3>";
    

PS Вы не можете зашифровать пустую строку ("")

PPS Это для базы паролей, а не для проверки пользователя.

Ответ 10

Я публикую это очень поздно, но я надеюсь, что это поможет кому-то еще, кто сталкивается с той же проблемой в версии PHP 7.2x.

Я использую это на PHP 7.2x, он отлично работает для меня.

public function make_hash($userStr){
        try{
            /** 
             * Used and tested on PHP 7.2x, Salt has been removed manually, it is now added by PHP 
             */
             return password_hash($userStr, PASSWORD_BCRYPT);
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

а затем аутентифицируйте хеш с помощью следующей функции:

public function varify_user($userStr,$hash){
        try{
            if (password_verify($userStr, $hash)) {
                 return true;
                }
            else {
                return false;
                }
            }catch(Exception $exc){
                $this->tempVar = $exc->getMessage();
                return false;
            }
        }

//Пример:

  //create hash from user string

 $user_password = $obj->make_hash2($user_key);

и для аутентификации этого хэша используйте следующий код:

if($obj->varify_user($key, $user_key)){
      //this is correct, you can proceed with  
    }

Это все

Ответ 11

Просто используйте @ перед каждым mcrypt например:

@mcrypt_module_open,
@mcrypt_get_block_size,
@mcrypt_generic_init
@mcrypt_generic
@mcrypt_generic_deinit

Он удалит функцию mcrypt_module_open и будет работать.