mcrypt已被弃用,有什么替代方案?

时间:2016-12-21 21:34:12

标签: php encryption passwords php-7 mcrypt

根据发布的评论deprecated,将在PHP 7.2中删除mcrypt-extension here。所以我正在寻找一种加密密码的替代方法。

现在我正在使用像

这样的东西
try: except:

我需要您对加密密码的最佳/最强方式的意见,加密密码当然应该由PHP 7.xx支持,并且还应该是可解密的,因为我的客户确实希望有一个选项来恢复&# 39;他们的密码没有生成新密码。

11 个答案:

答案 0 :(得分:37)

最佳做法是对密码进行哈希处理,使其无法解密。这使得可能已经获得对您的数据库或文件的访问权的攻击者更加困难。

如果您必须加密数据并使其可解密,则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]
  • 进行编译

答案 1 :(得分:20)

根据@rqLizard的建议,您可以使用openssl_encrypt / openssl_decrypt PHP函数代替  更好地替代实施AES(高级加密标准),也称为Rijndael加密。

根据以下Scott's comment at php.net

  

如果您要在2015年编写加密/加密数据的代码,则应使用openssl_encrypt()openssl_decrypt()。底层库(libmcrypt)自2007年以来就已被放弃,其性能远远低于OpenSSL(在现代处理器上利用AES-NI并且缓存时序安全)。

     

此外,MCRYPT_RIJNDAEL_256不是AES-256,它是Rijndael分组密码的不同变体。如果您想要AES-256中的mcrypt,则必须使用带有32字节密钥的MCRYPT_RIJNDAEL_128。 OpenSSL使您更清楚使用哪种模式(即aes-128-cbc vs aes-256-ctr)。

     

OpenSSL还使用带有CBC模式的PKCS7填充而不是mcrypt的NULL字节填充。因此,mcrypt更有可能使您的代码容易受到填充oracle攻击而不是OpenSSL。

     

最后,如果你没有验证你的密文(加密然后MAC),那么你做错了。

进一步阅读:

代码示例

示例#1

  

用于PHP 7.1 +的GCM模式示例中的AES认证加密

<?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

  

PHP 5.6 +的AES身份验证加密示例

<?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

根据以上示例,我更改了以下用于加密用户会话ID的代码:

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&#39;评论):

  • Rijndael 128 MCRYPT_RIJNDAEL_128 等同于 AES ,但 Rijndael 256 ({ {1}})不是 AES-256 ,因为256指定的块大小为256位,而 AES 只有一个块大小:128位。因此,由于mcrypt开发人员的选择,基本上Rijndael的块大小为256位(MCRYPT_RIJNDAEL_256)被错误地命名。 @zaph
  • 块大小为256的Rijndael可能不如128位的块大小安全,因为后者有更多的评论和使用。其次,互操作性受到阻碍,因为AES通常是可用的,而块大小为256位的Rijndael则不然。
  • Rijndael使用不同块大小的加密会产生不同的加密数据。

    例如,MCRYPT_RIJNDAEL_256(不等同于MCRYPT_RIJNDAEL_256)定义了Rijndael分组密码的不同变体,其大小为256位,密钥大小基于传入密钥,其中{{ 1}}是Rijndael,块大小为128位,密钥大小为256位。因此,他们使用不同的块大小来生成完全不同的加密数据,因为mcrypt使用该数字来指定块大小,其中OpenSSL使用该数字来指定密钥大小(AES只有一个块大小为128位)。所以基本上AES是Rijndael,块大小为128位,密钥大小为128,192和256位。因此,最好使用AES,在OpenSSL中称为Rijndael 128.

答案 2 :(得分:5)

Rijndael的Pure-PHP实现以phpseclib作为作曲者软件包存在,并且可在PHP 7.3(由我测试)上工作。

在phpseclib文档上有一个页面,在您输入基本变量(密码,模式,密钥大小,位大小)后generates sample code。它为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);

* $termbase64_decoded

答案 3 :(得分:4)

您可以使用phpseclib pollyfill包。你不能使用open ssl或libsodium来加密/解密rijndael 256。 另一个问题是,您不需要更换任何代码。

答案 4 :(得分:3)

您应该在OPENSSL_ZERO_PADDING之上使用OpenSSL,因为它是积极开发和维护的。它提供了更好的安全性,可维护性和可移植性。其次,它更快地执行AES加密/解密。它默认使用PKCS7填充,但如果需要,可以指定aes-256-cbc。要使用32字节的二进制密钥,您可以指定MCRYPT_RIJNDAEL_128,这比/** * 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'); } } 更明显。

以下是使用Mcrypt的代码示例:

  

使用PKCS7填充在Mcrypt中编写的未经身份验证的AES-256-CBC加密库。

/**
 * 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
        );
    }
}

这是使用OpenSSL编写的版本:

doSomething(func) {
 // do async code.
 // call func
 // func()
}

来源:If You're Typing the Word MCRYPT Into Your PHP Code, You're Doing It Wrong

答案 5 :(得分:1)

正如所指出的,你不应该存储你的用户&#39;密码采用可解密的格式。可逆加密为黑客提供了一条查找用户的简便途径。密码,扩展到让您的用户&#39;如果他们在那里使用相同的密码,那么在其他风险的网站上的帐户。

PHP为随机盐渍的单向散列加密提供了一对强大的功能 - password_hash()password_verify()。由于哈希是自动随机盐化的,因此黑客无法利用密码哈希的预编译表来对密码进行反向工程。设置PASSWORD_DEFAULT选项,PHP的未来版本将自动使用更强大的算法来生成密码哈希,而无需更新代码。

答案 6 :(得分:1)

您应该使用openssl_encrypt()功能。

答案 7 :(得分:1)

如此处其他答案所详述,我发现的最佳解决方案是使用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/

答案 8 :(得分:1)

我发布得很晚,但是我希望它能对其他在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  
    }

仅此而已

答案 9 :(得分:0)

我能够翻译我的加密对象

  • 使用mcrypt获取php的副本以解密旧数据。我去了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。根据我的阅读,使用RSA密钥的openssl_encrypt限制为比密钥长度少11个字节(参见Thomas Horsten的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>";
    

P.S。您无法加密空字符串(“”)

P.P.S。这是用于密码数据库而不是用于用户验证。

答案 10 :(得分:-4)

仅在每个@之前使用mcrypt,例如:

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

它将删除功能mcrypt_module_open贬值的错误,并且可以正常工作。