PHP AES加密/解密

68

我在 PHP 中找到了一个字符串编码/解码的示例。一开始看起来很好,但是它不起作用 :-(

有人知道问题出在哪里吗?

$Pass = "Passwort";
$Clear = "Klartext";

$crypted = fnEncrypt($Clear, $Pass);
echo "Encrypted: ".$crypted."</br>";

$newClear = fnDecrypt($crypted, $Pass);
echo "Decrypted: ".$newClear."</br>";

function fnEncrypt($sValue, $sSecretKey) {
    return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sDecrypted, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
}

function fnDecrypt($sValue, $sSecretKey) {
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sEncrypted), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));
}

结果是:

加密后的内容: boKRNTYYNp7AiOvY1CidqsAn9wX4ufz/D9XrpjAOPk8=

解密后的内容: —‚(ÑÁ ^ yË~F'¸®Ó–í œð2Á_B‰Â—


5
欧洲央行(ECB)不安全(通信协议中的CBC也是如此)。MCRYPT_RIJNDAEL_256 不是AES。 - Maarten Bodewes
以下是一个很好的例子,解释了如何使用MCrypt库在PHP中加密/解密数据:http://code-epicenter.com/how-to-use-mcrypt-library-in-php/ - MrD
由于被选中的答案被认为存在严重问题和不安全性,请将此问题的已接受答案移动。 - Yakk - Adam Nevraumont
10个回答

96

请使用现有的安全的PHP加密库

编写自己的加密算法通常是一个不好的主意,除非你有破解其他人密码实现的经验。

这里的任何示例都没有对密文进行认证处理,这使它们容易受到比特重写攻击。

如果您可以安装PECL扩展,libsodium会更好

<?php
// PECL libsodium 0.2.1 and newer

/**
 * Encrypt a message
 * 
 * @param string $message - message to encrypt
 * @param string $key - encryption key
 * @return string
 */
function safeEncrypt($message, $key)
{
    $nonce = \Sodium\randombytes_buf(
        \Sodium\CRYPTO_SECRETBOX_NONCEBYTES
    );

    return base64_encode(
        $nonce.
        \Sodium\crypto_secretbox(
            $message,
            $nonce,
            $key
        )
    );
}

/**
 * Decrypt a message
 * 
 * @param string $encrypted - message encrypted with safeEncrypt()
 * @param string $key - encryption key
 * @return string
 */
function safeDecrypt($encrypted, $key)
{   
    $decoded = base64_decode($encrypted);
    $nonce = mb_substr($decoded, 0, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
    $ciphertext = mb_substr($decoded, \Sodium\CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');

    return \Sodium\crypto_secretbox_open(
        $ciphertext,
        $nonce,
        $key
    );
}    

然后进行测试:

<?php
// This refers to the previous code block.
require "safeCrypto.php"; 

// Do this once then store it somehow:
$key = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';

$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

这可以在任何需要向客户端传递数据的情况下使用(例如,对于没有服务器端存储的会话的加密 cookie,加密 URL 参数等),可以相当高度地确保最终用户无法破解或可靠地篡改它。

由于libsodium是跨平台的,这也使得与PHP通信变得更容易,例如从Java小程序或本地移动应用程序。


注意:如果您特别需要向应用程序添加由libsodium支持的加密cookie,则我的雇主Paragon Initiative Enterprises正在开发一个名为Halite的库,可以为您完成所有这些工作。


您提供的库返回二进制格式的加密消息 - 正确吗?是否可能以简单字符串格式返回?谢谢。 - Andrew
这个GitHub仓库中提供了C# .NET的端口,如果有人需要的话:https://github.com/mayerwin/SaferCrypto。感谢@ScottArciszewski。 - Erwin Mayer
不是的@Andrew,它返回使用base64编码的消息,这是一个简单的字符串。 - Riking

78

如果您不想使用一个重量级的依赖来解决15行代码就能解决的问题,可以使用内置的OpenSSL函数。大多数PHP安装都自带了OpenSSL,它在PHP中提供了快速、兼容和安全的AES加密。只要您遵循最佳实践,它就是安全的。

以下代码:

  • 使用CBC模式的AES256
  • 与其他AES实现兼容,但不兼容mcrypt,因为mcrypt使用PKCS#5而不是PKCS#7。
  • 使用SHA256从提供的密码生成密钥
  • 对加密数据生成HMAC哈希以进行完整性检查
  • 为每条消息生成随机IV
  • 将IV(16字节)和哈希(32字节)前置到密文中
  • 应该相当安全

IV是公共信息,需要为每条消息随机生成。哈希确保数据没有被篡改。

function encrypt($plaintext, $password) {
    $method = "AES-256-CBC";
    $key = hash('sha256', $password, true);
    $iv = openssl_random_pseudo_bytes(16);

    $ciphertext = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv);
    $hash = hash_hmac('sha256', $ciphertext . $iv, $key, true);

    return $iv . $hash . $ciphertext;
}

function decrypt($ivHashCiphertext, $password) {
    $method = "AES-256-CBC";
    $iv = substr($ivHashCiphertext, 0, 16);
    $hash = substr($ivHashCiphertext, 16, 32);
    $ciphertext = substr($ivHashCiphertext, 48);
    $key = hash('sha256', $password, true);

    if (!hash_equals(hash_hmac('sha256', $ciphertext . $iv, $key, true), $hash)) return null;

    return openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
}

使用方法:

$encrypted = encrypt('Plaintext string.', 'password'); // this yields a binary string

echo decrypt($encrypted, 'password');
// decrypt($encrypted, 'wrong password') === null

编辑:更新为使用hash_equals函数并将IV添加到哈希中。


3
使用等号比较哈希值并不是一个好的做法,你应该使用hash_equals()函数,因为使用等号容易受到时序攻击,更多信息请参见这里 - Shahin
1
这个答案几乎就是那样了,但还需要改进...1)更好的 KDF,SHA-256 是非常差的 KDF。至少要使用 PBKDF2,但 Argon2 / bcrypt 更好。 2)IV 需要包含在 HMAC 中 - HMAC 的目的是确保解密结果为明文或失败 - 不包括 IV 会导致用户 认为 他们正在获取原始明文,但实际上并不是。 3)在比较哈希值时使用安全的时间比较,否则此代码 可能 易受时序攻击。 4)不要将用于 HMAC 的密钥与用于 AES 的密钥相同。 - Luke Joshua Park
1
@MikkoRantalainen 我们不能假设密钥是安全的,特别是因为它不是一个密钥,而是一个由人选择的低熵密码。如果我们使用 SHA-256 来派生加密密钥,这只需要非常微不足道的时间,那么对密码进行暴力攻击就变得非常容易。然而,如果我们使用 PBKDF2 或 Argon2,我们可以微调派生密码所需的时间(大约几百毫秒),那么暴力破解就变得不太可行了。这基本上是我们不会使用 SHA-256 作为密码哈希的完全相同的原因。 - Luke Joshua Park
1
如果您想将此存储在数据库中,应对加密函数的返回值执行base64_encode操作,并对解密函数的$ivHashCiphertext值执行base64_decode操作。否则,您可能会遇到与数据库编码相关的问题。 - Chris
1
@naman1994 输出的是二进制数据块,因此您需要将其编码为纯文本 - 即使用base64_encode、bin2hex等方法。 - blade
显示剩余14条评论

59

$sDecrypted$sEncrypted在你的代码中未定义。请看下面的解决方案(但不安全!):


STOP!

此示例不安全!请勿使用。


$Pass = "Passwort";
$Clear = "Klartext";        

$crypted = fnEncrypt($Clear, $Pass);
echo "Encrypred: ".$crypted."</br>";

$newClear = fnDecrypt($crypted, $Pass);
echo "Decrypred: ".$newClear."</br>";        

function fnEncrypt($sValue, $sSecretKey)
{
    return rtrim(
        base64_encode(
            mcrypt_encrypt(
                MCRYPT_RIJNDAEL_256,
                $sSecretKey, $sValue, 
                MCRYPT_MODE_ECB, 
                mcrypt_create_iv(
                    mcrypt_get_iv_size(
                        MCRYPT_RIJNDAEL_256, 
                        MCRYPT_MODE_ECB
                    ), 
                    MCRYPT_RAND)
                )
            ), "\0"
        );
}

function fnDecrypt($sValue, $sSecretKey)
{
    return rtrim(
        mcrypt_decrypt(
            MCRYPT_RIJNDAEL_256, 
            $sSecretKey, 
            base64_decode($sValue), 
            MCRYPT_MODE_ECB,
            mcrypt_create_iv(
                mcrypt_get_iv_size(
                    MCRYPT_RIJNDAEL_256,
                    MCRYPT_MODE_ECB
                ), 
                MCRYPT_RAND
            )
        ), "\0"
    );
}

但是这段代码存在其他问题,使得它不安全,特别是使用ECB(它不是一种加密模式,只是定义加密模式的基础构件)。参见Fab Sa的回答,快速修复最严重的问题,以及Scott的回答,了解如何正确做。


17
我正在使用这段代码,但发现了一个错误。不应该使用trim()!!!应该使用带有第二个参数“\0”的rtrim()。在极少数情况下,加密值的第一个或最后一个字符可能是空格或换行符,解密会出错... - Paul Jacobse
3
你能再混淆一下吗?我担心即使费些力气,我仍然可以读懂你的代码。 </sarcasm mode> 代码应该是自描述的。给像mcrypt_get_iv_size的输出一个适当的变量名,然后使用它。这种缩进很难阅读。除非你习惯于Lisp,但大多数PHP程序员不会。我并不是说提问者的代码更好,但作为一个好的回答,你可能也可以改进代码。 - Luc
6
ECB模式不安全,建议使用其他模式。参考维基百科条目《分组密码工作模式》了解更多详情。 - Polynomial
9
encryptdecrypt函数中传递不同的IV没有任何意义。这能够运行的唯一原因是ECB模式根本不使用初始化向量,因此任何值都可以产生相同的输出。 - Clément
4
我不知道为什么这个答案被赞了这么多。仅仅因为“它可以工作”并不意味着它是安全的,也不应该在生产环境中使用MCRYPT_MODE_ECB模式的加密。即使PHP的mcrypt_ecb函数已经在PHP 5.5.0版本中被弃用。依赖于此函数是极其不鼓励的。相反,你应该使用MCRYPT_MODE_CBC模式:http://wpy.me/blog/15-encrypt-and-decrypt-data-in-php-using-aes-256 - wappy
显示剩余5条评论

27

关于 MCRYPT_MODE_ECB 的信息:它不使用IV(初始向量)。ECB模式将您的消息分成块,每个块单独加密。我真的不建议使用

CBC模式使用IV来使每个消息唯一。建议使用CBC而不是ECB。

示例:

<?php
$password = "myPassword_!";
$messageClear = "Secret message";

// 32 byte binary blob
$aes256Key = hash("SHA256", $password, true);

// for good entropy (for MCRYPT_RAND)
srand((double) microtime() * 1000000);
// generate random iv
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND);


$crypted = fnEncrypt($messageClear, $aes256Key);

$newClear = fnDecrypt($crypted, $aes256Key);

echo
"IV:        <code>".$iv."</code><br/>".
"Encrypred: <code>".$crypted."</code><br/>".
"Decrypred: <code>".$newClear."</code><br/>";

function fnEncrypt($sValue, $sSecretKey) {
    global $iv;
    return rtrim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sValue, MCRYPT_MODE_CBC, $iv)), "\0\3");
}

function fnDecrypt($sValue, $sSecretKey) {
    global $iv;
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sValue), MCRYPT_MODE_CBC, $iv), "\0\3");
}

要解码每个消息,您需要存储 IV (IV 不是秘密)。每个消息都是独特的,因为每个消息都有一个唯一的 IV。


1
你应该添加一个例子来阐明aes256Key的用法。这个例子展示了如何使用它:http://www.php.net/manual/fr/book.mcrypt.php#107483 - mgutt
1
它基本相同,但我的示例为了清晰起见没有使用盐。 - Fabien Sa
2
最佳答案生成随机IV以供不需要任何IV的系统(ECB)使用。 - Clément
如果我理解正确的话,$password = "myPassword_!" 就成为了加密算法的一部分,对吗? - user3746998
4
请注意,上面的代码不使用AES也不使用PKCS#7填充,这意味着它将与任何其他系统不兼容。我是为mcrypt_encrypt修复示例代码的人。 - Maarten Bodewes

5

这是一个使用openssl实现的 AES加密 的可行解决方案。它使用密码块链接模式(CBC模式)。因此,除了 datakey之外,您还可以指定iv块大小

 <?php
      class AESEncryption {

            protected $key;
            protected $data;
            protected $method;
            protected $iv;

            /**
             * Available OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
             *
             * @var type $options
             */
            protected $options = 0;

            /**
             * 
             * @param type $data
             * @param type $key
             * @param type $iv
             * @param type $blockSize
             * @param type $mode
             */
            public function __construct($data = null, $key = null, $iv = null, $blockSize = null, $mode = 'CBC') {
                $this->setData($data);
                $this->setKey($key);
                $this->setInitializationVector($iv);
                $this->setMethod($blockSize, $mode);
            }

            /**
             * 
             * @param type $data
             */
            public function setData($data) {
                $this->data = $data;
            }

            /**
             * 
             * @param type $key
             */
            public function setKey($key) {
                $this->key = $key;
            }

            /**
             * CBC 128 192 256 
              CBC-HMAC-SHA1 128 256
              CBC-HMAC-SHA256 128 256
              CFB 128 192 256
              CFB1 128 192 256
              CFB8 128 192 256
              CTR 128 192 256
              ECB 128 192 256
              OFB 128 192 256
              XTS 128 256
             * @param type $blockSize
             * @param type $mode
             */
            public function setMethod($blockSize, $mode = 'CBC') {
                if($blockSize==192 && in_array('', array('CBC-HMAC-SHA1','CBC-HMAC-SHA256','XTS'))){
                    $this->method=null;
                    throw new Exception('Invalid block size and mode combination!');
                }
                $this->method = 'AES-' . $blockSize . '-' . $mode;
            }

            /**
             * 
             * @param type $data
             */
            public function setInitializationVector($iv) {
                $this->iv = $iv;
            }

            /**
             * 
             * @return boolean
             */
            public function validateParams() {
                if ($this->data != null &&
                        $this->method != null ) {
                    return true;
                } else {
                    return FALSE;
                }
            }

            //it must be the same when you encrypt and decrypt
            protected function getIV() { 
                return $this->iv;
            }

             /**
             * @return type
             * @throws Exception
             */
            public function encrypt() {
                if ($this->validateParams()) { 
                    return trim(openssl_encrypt($this->data, $this->method, $this->key, $this->options,$this->getIV()));
                } else {
                    throw new Exception('Invalid params!');
                }
            }

            /**
             * 
             * @return type
             * @throws Exception
             */
            public function decrypt() {
                if ($this->validateParams()) {
                   $ret=openssl_decrypt($this->data, $this->method, $this->key, $this->options,$this->getIV());

                   return   trim($ret); 
                } else {
                    throw new Exception('Invalid params!');
                }
            }

        }

示例用法:

<?php
        $data = json_encode(['first_name'=>'Dunsin','last_name'=>'Olubobokun','country'=>'Nigeria']);
        $inputKey = "W92ZB837943A711B98D35E799DFE3Z18";
        $iv = "tuqZQhKP48e8Piuc";
        $blockSize = 256;
        $aes = new AESEncryption($data, $inputKey, $iv, $blockSize);
        $enc = $aes->encrypt();
        $aes->setData($enc);
        $dec=$aes->decrypt();
        echo "After encryption: ".$enc."<br/>";
        echo "After decryption: ".$dec."<br/>";

1
这段代码将 IV 处理留给用户(用户会处理得很糟糕),也没有包含任何完整性检查。不是好的加密代码。 - Luke Joshua Park

5

这是使用AES256 CBC在PHP中加密/解密字符串的简洁方法:

function encryptString($plaintext, $password, $encoding = null) {
    $iv = openssl_random_pseudo_bytes(16);
    $ciphertext = openssl_encrypt($plaintext, "AES-256-CBC", hash('sha256', $password, true), OPENSSL_RAW_DATA, $iv);
    $hmac = hash_hmac('sha256', $ciphertext.$iv, hash('sha256', $password, true), true);
    return $encoding == "hex" ? bin2hex($iv.$hmac.$ciphertext) : ($encoding == "base64" ? base64_encode($iv.$hmac.$ciphertext) : $iv.$hmac.$ciphertext);
}

function decryptString($ciphertext, $password, $encoding = null) {
    $ciphertext = $encoding == "hex" ? hex2bin($ciphertext) : ($encoding == "base64" ? base64_decode($ciphertext) : $ciphertext);
    if (!hash_equals(hash_hmac('sha256', substr($ciphertext, 48).substr($ciphertext, 0, 16), hash('sha256', $password, true), true), substr($ciphertext, 16, 32))) return null;
    return openssl_decrypt(substr($ciphertext, 48), "AES-256-CBC", hash('sha256', $password, true), OPENSSL_RAW_DATA, substr($ciphertext, 0, 16));
}

使用方法:

$enc = encryptString("mysecretText", "myPassword");
$dec = decryptString($enc, "myPassword");

编辑: 这是一个新版本的函数,使用 AES256 GCMPBKDF2 作为密钥派生方式,更加安全。

function str_encryptaesgcm($plaintext, $password, $encoding = null) {
    if ($plaintext != null && $password != null) {
        $keysalt = openssl_random_pseudo_bytes(16);
        $key = hash_pbkdf2("sha512", $password, $keysalt, 20000, 32, true);
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-gcm"));
        $tag = "";
        $encryptedstring = openssl_encrypt($plaintext, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag, "", 16);
        return $encoding == "hex" ? bin2hex($keysalt.$iv.$encryptedstring.$tag) : ($encoding == "base64" ? base64_encode($keysalt.$iv.$encryptedstring.$tag) : $keysalt.$iv.$encryptedstring.$tag);
    }
}

function str_decryptaesgcm($encryptedstring, $password, $encoding = null) {
    if ($encryptedstring != null && $password != null) {
        $encryptedstring = $encoding == "hex" ? hex2bin($encryptedstring) : ($encoding == "base64" ? base64_decode($encryptedstring) : $encryptedstring);
        $keysalt = substr($encryptedstring, 0, 16);
        $key = hash_pbkdf2("sha512", $password, $keysalt, 20000, 32, true);
        $ivlength = openssl_cipher_iv_length("aes-256-gcm");
        $iv = substr($encryptedstring, 16, $ivlength);
        $tag = substr($encryptedstring, -16);
        return openssl_decrypt(substr($encryptedstring, 16 + $ivlength, -16), "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag);
    }
}

使用方法:

$enc = str_encryptaesgcm("mysecretText", "myPassword", "base64"); // return a base64 encrypted string, you can also choose hex or null as encoding.
$dec = str_decryptaesgcm($enc, "myPassword", "base64");

我发现这个方法可行,而上面的其他方法会产生奇怪的字符,并且无法解密成可读的内容。 - WilliamK
我发现这个有时候工作,有时候不工作。每次加密后都会产生不同的结果。有时候无法解密。 - WilliamK
尝试这个:encryptString("mysecretText", "myPassword", "hex") | decryptString($enc, "myPassword", "hex") @WilliamK - Marco Concas
我使用crypto-es在Javascript中加密字符串,想要使用您的函数在PHP中解密,但它返回null。JS和PHP中的密码短语相同。我已经使用'base64'设置了编码参数,但没有成功。我可能错过了什么? - Jeaf Gilbert
我应该检查你在JS中使用的函数以了解问题,不管怎样,我已经用C#测试过了,一切都完美运行。 - Marco Concas

2
AES加密需要注意的几个重要事项:
  1. 不要使用明文作为加密密钥。始终对明文密钥进行哈希处理,然后再用于加密。
  2. 在加密和解密过程中始终使用随机初始化向量(IV)。真正的随机化非常重要。
  3. 如上所述,不要使用模式,而应改用CBC模式。

仅仅对密码进行哈希处理作为加密密钥是不够的,参见 blade 回答中的评论。 - Luke Joshua Park

1

这是一个改进版本,基于blade编写的代码

  • 添加注释
  • 在抛出异常之前覆盖参数,以避免泄露秘密
  • 检查openssl和hmac函数的返回值

代码如下:

class Crypto
{
    /**
     * Encrypt data using OpenSSL (AES-256-CBC)
     * @param string $plaindata Data to be encrypted
     * @param string $cryptokey key for encryption (with 256 bit of entropy)
     * @param string $hashkey key for hashing (with 256 bit of entropy)
     * @return string IV+Hash+Encrypted as raw binary string. The first 16
     *     bytes is IV, next 32 bytes is HMAC-SHA256 and the rest is
     *     $plaindata as encrypted.
     * @throws Exception on internal error
     *
     * Based on code from: https://dev59.com/33A75IYBdhLWcg3wMmCo#46872528
     */
    public static function encrypt($plaindata, $cryptokey, $hashkey)
    {
        $method = "AES-256-CBC";
        $key = hash('sha256', $cryptokey, true);
        $iv = openssl_random_pseudo_bytes(16);

        $cipherdata = openssl_encrypt($plaindata, $method, $key, OPENSSL_RAW_DATA, $iv);

        if ($cipherdata === false)
        {
            $cryptokey = "**REMOVED**";
            $hashkey = "**REMOVED**";
            throw new \Exception("Internal error: openssl_encrypt() failed:".openssl_error_string());
        }

        $hash = hash_hmac('sha256', $cipherdata.$iv, $hashkey, true);

        if ($hash === false)
        {
            $cryptokey = "**REMOVED**";
            $hashkey = "**REMOVED**";
            throw new \Exception("Internal error: hash_hmac() failed");
        }

        return $iv.$hash.$cipherdata;
    }

    /**
    * Decrypt data using OpenSSL (AES-256-CBC)
     * @param string $encrypteddata IV+Hash+Encrypted as raw binary string
     *     where the first 16 bytes is IV, next 32 bytes is HMAC-SHA256 and
     *     the rest is encrypted payload.
     * @param string $cryptokey key for decryption (with 256 bit of entropy)
     * @param string $hashkey key for hashing (with 256 bit of entropy)
     * @return string Decrypted data
     * @throws Exception on internal error
     *
     * Based on code from: https://dev59.com/33A75IYBdhLWcg3wMmCo#46872528
     */
    public static function decrypt($encrypteddata, $cryptokey, $hashkey)
    {
        $method = "AES-256-CBC";
        $key = hash('sha256', $cryptokey, true);
        $iv = substr($encrypteddata, 0, 16);
        $hash = substr($encrypteddata, 16, 32);
        $cipherdata = substr($encrypteddata, 48);

        if (!hash_equals(hash_hmac('sha256', $cipherdata.$iv, $hashkey, true), $hash))
        {
            $cryptokey = "**REMOVED**";
            $hashkey = "**REMOVED**";
            throw new \Exception("Internal error: Hash verification failed");
        }

        $plaindata = openssl_decrypt($cipherdata, $method, $key, OPENSSL_RAW_DATA, $iv);

        if ($plaindata === false)
        {
            $cryptokey = "**REMOVED**";
            $hashkey = "**REMOVED**";
            throw new \Exception("Internal error: openssl_decrypt() failed:".openssl_error_string());
        }

        return $plaindata;
    }
}

如果您确实无法使用适当的加密和哈希密钥,而只能使用用户输入的密码作为唯一的秘密,可以采用以下方法:
/**
 * @param string $password user entered password as the only source of
 *   entropy to generate encryption key and hash key.
 * @return array($encryption_key, $hash_key) - note that PBKDF2 algorithm
 *   has been configured to take around 1-2 seconds per conversion
 *   from password to keys on a normal CPU to prevent brute force attacks.
 */
public static function generate_encryptionkey_hashkey_from_password($password)
{
    $hash = hash_pbkdf2("sha512", "$password", "salt$password", 1500000);
    return str_split($hash, 64);
}

编辑得很好,看起来不错!唯一的问题是,正如我们之前讨论过的那样,这个程序容易受到暴力破解攻击,因为我们信任用户提供具有足够熵的“加密密钥”。这个问题可以通过使用真正的KDF而不是SHA-256来解决。除此之外,看起来很不错! - Luke Joshua Park
@LukeJoshuaPark:是的,我认为这些方法将使用真实密钥进行低级别实现。也许我应该添加一种使用密钥派生函数(KDF)从用户密码到加密密钥的方法。然而,这种方法不应声称可以通过低质量的用户密码神奇地具有256位熵。相反,KDF在逻辑上是从例如32位密钥到256位密钥空间的注入,攻击者没有简单枚举256位密钥空间中所有2^32个可能密钥的简单方法。 - Mikko Rantalainen
假设我们只有密码(没有盐的存储),那么KDF需要像hash_pbkdf2("sha256", $password, $password, 500000)这样的东西。当我们考虑GPU上SHA-256的性能时,我不确定即使使用低质量密码是否足够。 - Mikko Rantalainen
@LukeJoshuaPark,你认为从同一个密码生成哈希密钥和加密密钥是否可行?例如 $hash_key = hash_pbkdf2("sha256", "$password", "hash$password", 500000)$encryption_key = hash_pbkdf2("sha256", $password, "enc$password", 500000) - Mikko Rantalainen
1
是的 - 虽然如果您要这样做,我建议使用SHA-512运行PBKDF2,而不是SHA-256。这允许输出的前256位成为加密密钥,输出的后256位成为哈希密钥。 - Luke Joshua Park

1
如果您正在使用MCRYPT_RIJNDAEL_128,请尝试rtrim($output, "\0\3")。如果字符串长度小于16,则解密函数将返回一个长度为16个字符的字符串,在末尾添加03。
例如,您可以轻松检查这一点,通过尝试:
$string = "TheString";
$decrypted_string = decrypt_function($stirng, $key);

echo bin2hex($decrypted_string)."=".bin2hex("TheString");

使用MCRYPT_RIJNDAEL_128,这对我有用:rtrim($output, "\x00..\x1F") - Sergio Viudes

-1
如果您使用的是 PHP >= 7.2,请考虑使用内置的钠核心扩展进行加密。
在此处查找更多信息 - http://php.net/manual/en/intro.sodium.php

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接