不推荐使用 mcrypt,还有其他选择吗?

Mcrypt 扩展名是 不赞成,在 PHP 7.2中将根据发布的 给你评论删除该扩展名。因此,我正在寻找一种替代方法来加密密码。

现在我用的是

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

我需要您的意见最佳/最强的方式来加密密码,加密的密码当然应该支持 PHP 7。还应该是可解密的,因为我的客户确实希望有一个选项来“恢复”他们的密码,而不生成一个新的。

198635 次浏览

最佳实践是散列密码,这样它们就不可解密了。这使得可能已经访问了您的数据库或文件的攻击者的处理稍微有些困难。

如果必须对数据进行加密并使其可解密,https://paragonie.com/white-paper/2015-secure-php-data-encryption提供了安全加密/解密指南。总结一下这种联系:

  • 使用 锂钠-一个 PHP 扩展
  • If you can't use Libsodium, use Deuse/php-加密 - Straight PHP code
  • If you can't use Libsodium or defuse/php-encryption, use OpenSSL - A lot of servers will already have this installed. If not, it can be compiled with --with-openssl[=DIR]

正如指出的那样,不应该以可解密的格式存储用户的密码。可逆加密技术为黑客提供了一条查找用户密码的便捷途径,如果用户在其他网站使用相同的密码,这种加密技术还可以将用户的账户置于风险之中。

PHP 为随机加盐的单向散列加密提供了一对强大的函数。因为散列是自动随机加盐的,所以黑客无法利用预编译的密码散列表对密码进行反向工程。设置 PASSWORD_DEFAULT选项,PHP 的未来版本将自动使用更强大的算法来生成密码哈希,而无需更新代码。

You can use Phpseclib pollyfill package. You can not use open ssl or libsodium for encrypt/decrypt with rijndael 256. 另一个问题是,您不需要替换任何代码。

应该使用 openssl_encrypt()函数。

我翻译了我的加密对象

  • 使用 mcrypt 获取一个 php 副本来解密旧数据。我转到 http://php.net/get/php-7.1.12.tar.gz/from/a/mirror,编译它,然后添加 ext/mcrypt 扩展(configure; make; make install)。我认为我还必须向 php.ini 添加扩展名 = mcrypt.so 行。用于生成所有数据未加密的数据的中间版本的一系列脚本。

  • 为 openssl 构建一个公钥和私钥

    openssl genrsa -des3 -out pkey.pem 2048
    (set a password)
    openssl rsa -in pkey.pem -out pkey-pub.pem -outform PEM -pubout
    
  • To Encrypt (using public key) use openssl_seal. From what I've read, openssl_encrypt using an RSA key is limited to 11 bytes less than the key length (See http://php.net/manual/en/function.openssl-public-encrypt.php comment by Thomas Horsten)

    $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]);
    

You could probably store the raw binary.

  • To Decrypt (using private key)

    $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. You can't encrypt the empty string ("")

P.P.S. This is for a password database not for user validation.

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

根据以下 Scott 在 php.net 上的评论:

如果您在2015年编写加密/加密数据的代码,那么应该使用 openssl_encrypt()openssl_decrypt()。底层库(libmcrypt)自2007年以来就被放弃了,它的性能比 OpenSSL 差得多(OpenSSL 在现代处理器上利用了 AES-NI,并且缓存计时安全)。

而且,MCRYPT_RIJNDAEL_256不是 AES-256,它是 Rijndael 块密码的另一种变体。如果想在 mcrypt中使用 AES-256,则必须使用具有32字节密钥的 MCRYPT_RIJNDAEL_128。OpenSSL 使您正在使用的模式更加明显(即 aes-128-cbcaes-256-ctr)。

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

Finally, if you are not authenticating your ciphertexts (Encrypt Then MAC), you're doing it wrong.

进一步阅读:

代码示例

例 # 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 < a href = “ https://stackoverflow. com/questions/41272257/mcrypt-is-depreated-what-is-the-Alternative/48120485? noredirect = 1 # comments 83219538 _ 48120485”>@zaph


附加说明(来自@zaph 的评论) :

  • Rijndael 128 (MCRYPT_RIJNDAEL_128) is相当于 AES,然而 Rijndael 256(MCRYPT_RIJNDAEL_256) is not AES-256作为256指定块大小为256位,而 AES只有一个块大小: 128位。因此,基本上 Rijndael 的块大小为256位(MCRYPT_RIJNDAEL_256)已经被错误地命名为由于 Mcrypt开发人员的选择。< sup >@zaph < sup >@zaph
  • 块大小为256的 Rijndael 可能不如块大小为128位的 Rijndael 安全,因为后者有更多的评论和使用。其次,互操作性受到阻碍,因为 AES 通常是可用的,而256位块大小的 Rijndael 是不可用的。
  • 对 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,在 OpenSSL 中称为 Rijndael 128。

您应该在 mcrypt之上使用 OpenSSL,因为它是积极开发和维护的。它提供了更好的安全性、可维护性和可移植性。其次,它执行 AES 加密/解密快得多。它默认使用 PKCS7填充,但是如果需要,可以指定 OPENSSL_ZERO_PADDING。要与32字节的二进制密钥一起使用,可以指定比 MCRYPT_RIJNDAEL_128明显得多的 aes-256-cbc

下面是使用 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
);
}
}

资料来源: 如果您在 PHP 代码中输入了单词 MCRYPT,那么您的做法是错误的

纯 PHP 的 Rijndael 实现与 phpseclib可作为作曲家软件包和工作在 PHP 7.3(由我测试)。

在 phpseclib 文档中有一个页面,在输入基本变量(密码、模式、密钥大小、位大小)之后,会显示 生成示例代码。它为 Rijndael,ECB,256,256输出以下数据:

一个密码

$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

正如这里的其他答案所详述的,我发现的最佳解决方案是使用 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);
}

Reference link: https://www.shift8web.ca/2017/04/how-to-encrypt-and-execute-your-php-code-with-mcrypt/

我在 PHP 7.2. x 上使用它,它对我来说工作得很好:

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
}

仅此而已。