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

2022-08-30 07:15:08

mcrypt-extension被弃用,根据这里发布的评论,将在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,请使用defortuse/php-encryption - Straight PHP代码
  • 如果你不能使用Libsodium或defiuse/php加密,请使用OpenSSL - 很多服务器已经安装了这个。如果没有,可以用 --with-openssl[=DIR] 编译

答案 2

正如@rqLizard所建议的那样,您可以使用openssl_encrypt/openssl_decrypt PHP 函数来代替,这为实现 AES(高级加密标准)(也称为 Rijndael 加密)提供了更好的替代方案。

根据斯科特在 php.net 上的评论

如果要在 2015 年编写代码来加密/加密数据,则应使用 和 。底层库()自2007年以来已被废弃,其性能远不如OpenSSL(它利用现代处理器并且是缓存计时安全的)。openssl_encrypt()openssl_decrypt()libmcryptAES-NI

另外,不是 ,它是Rijndael分组密码的不同变体。如果要 in,则必须使用 32 字节的密钥。OpenSSL使您正在使用的模式更加明显(即 与 )。MCRYPT_RIJNDAEL_256AES-256AES-256mcryptMCRYPT_RIJNDAEL_128aes-128-cbcaes-256-ctr

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

最后,如果您没有对密文进行身份验证(然后加密MAC),那么您做错了。

延伸阅读:

代码示例

示例 #1

GCM 模式下的 AES 身份验证加密示例,适用于 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

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();
  }

}

为了澄清,上述更改不是真正的转换,因为两种加密使用不同的块大小和不同的加密数据。此外,默认填充不同,仅支持非标准空填充。@zaphMCRYPT_RIJNDAEL


补充说明(来自@zaph的评论):

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

    例如,(不等效于 )定义了 Rijndael 块密码的不同变体,大小为 256 位,密钥大小基于传入密钥,其中 Rijndael 的块大小为 128 位,密钥大小为 256 位。因此,他们使用不同的块大小,生成完全不同的加密数据,因为mcrypt使用数字来指定块大小,而OpenSSL使用数字来指定密钥大小(AES只有一个128位的块大小)。因此,基本上AES是Rijndael,块大小为128位,密钥大小为128,192和256位。因此,最好使用AES,它在OpenSSL中被称为Rijndael 128。MCRYPT_RIJNDAEL_256AES-256aes-256-cbc


推荐