【问题标题】:openssl_encrypt() randomly fails - IV passed is only ${x} bytes long, cipher expects an IV of precisely 16 bytesopenssl_encrypt() 随机失败 - 传递的 IV 仅 ${x} 字节长,密码期望 IV 恰好为 16 个字节
【发布时间】:2016-09-23 05:25:11
【问题描述】:

这是我用来加密/解密数据的代码:

// Set the method
$method = 'AES-128-CBC';

// Set the encryption key
$encryption_key = 'myencryptionkey';

// Generet a random initialisation vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));

// Define the date to be encrypted
$data = "Encrypt me, please!";

var_dump("Before encryption: $data");

// Encrypt the data
$encrypted = openssl_encrypt($data, $method, $encryption_key, 0, $iv);

var_dump("Encrypted: ${encrypted}");

// Append the vector at the end of the encrypted string
$encrypted = $encrypted . ':' . $iv;

// Explode the string using the `:` separator.
$parts = explode(':', $encrypted);

// Decrypt the data
$decrypted = openssl_decrypt($parts[0], $method, $encryption_key, 0, $parts[1]);

var_dump("Decrypted: ${decrypted}");

它通常工作正常,但有时(十分之一甚至更少)它会失败。当它失败时,文本仅被部分加密:

这是发生时的错误消息:

Warning: openssl_decrypt(): IV passed is only 10 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0

当它发生时,加密文本看起来像:

Encrypt me���L�se!

我认为这可能是由 PHP 中的错误引起的,但我已经在不同的主机上进行了测试:PHP 7.0.6 和 PHP 5.6。我还尝试了多个在线 PHP 解析器,例如 phpfidle.org 或 3v4l.org。

openssl_random_pseudo_bytes 似乎并不总是返回适当长度的字符串,但我不知道为什么。

示例如下:https://3v4l.org/RZV8d

继续刷新页面,你会在某个时候收到错误消息。

【问题讨论】:

    标签: php encryption aes encryption-symmetric php-openssl


    【解决方案1】:

    当你生成一个随机 IV 时,你会得到raw binary。二进制字符串包含:\0 字符的可能性非零,您使用该字符将IV 与密文分开。这样做会使explode() 给你一个更短的字符串。演示:https://3v4l.org/3ObfJ

    简单的解决方案是在此过程中添加 base64 编码/解码。


    也就是说,please don't roll your own crypto。特别是unauthenticated encryption is dangerousthere are already secure libraries that solve this problem

    与其自己编写,不如考虑使用defuse/php-encryption。这是安全的选择。

    【讨论】:

    • 感谢您的解释。现在,至少,我知道是什么导致了这个问题,但我仍然不确定如何解决它。如果我将:$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method)); 替换为:$iv = base64_encode(openssl_random_pseudo_bytes(openssl_cipher_iv_length($method)));,那么字符串长度不会是 16 个字节。 openssl_encrypt 预计长度为 16 个字节(在 AES-128-CBC 方法的情况下)。我错过了什么吗?
    • 在传递给openssl_encrypt()之前不要编码,你把$encrypted = $encrypted . ':' . $iv;改成$encrypted = $encrypted . ':' . base64_encode($iv);
    • 天啊,太简单了。非常感谢!奇迹般有效。我还将整个加密数据包装在 base64_encode() 中,以使其对 URL 友好(就像@Ryan Vincent 所说的那样。)。我已经更新了原帖。
    • 嗨!,$IV_64 = 'AAAAAAAAABBBBAAAAAAAAA=='; “只有 15 个字节长,密码需要一个精确的 16 个字节的 IV,用 \0 填充” 当我用 base64_decode($IV_64) 调用 open_ssl 加密意味着原始 IV 不是 16 个字节长?
    【解决方案2】:

    这是解决方案

    我已经更新了第一篇文章中的代码并将其封装在一个类中。这是基于 Scott Arciszewski 提供的解决方案的固定代码。

    class Encryptor
    {
    
        /**
         * Holds the Encryptor instance
         * @var Encryptor
         */
        private static $instance;
    
        /**
         * @var string
         */
        private $method;
    
        /**
         * @var string
         */
        private $key;
    
        /**
         * @var string
         */
        private $separator;
    
        /**
         * Encryptor constructor.
         */
        private function __construct()
        {
            $app = App::getInstance();
            $this->method = $app->getConfig('encryption_method');
            $this->key = $app->getConfig('encryption_key');
            $this->separator = ':';
        }
    
        private function __clone()
        {
        }
    
        /**
         * Returns an instance of the Encryptor class or creates the new instance if the instance is not created yet.
         * @return Encryptor
         */
        public static function getInstance()
        {
            if (self::$instance === null) {
                self::$instance = new Encryptor();
            }
            return self::$instance;
        }
    
        /**
         * Generates the initialization vector
         * @return string
         */
        private function getIv()
        {
            return openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->method));
        }
    
        /**
         * @param string $data
         * @return string
         */
        public function encrypt($data)
        {
            $iv = $this->getIv();
            return base64_encode(openssl_encrypt($data, $this->method, $this->key, 0, $iv) . $this->separator . base64_encode($iv));
        }
    
        /**
         * @param string $dataAndVector
         * @return string
         */
        public function decrypt($dataAndVector)
        {
            $parts = explode($this->separator, base64_decode($dataAndVector));
            // $parts[0] = encrypted data
            // $parts[1] = initialization vector
            return openssl_decrypt($parts[0], $this->method, $this->key, 0, base64_decode($parts[1]));
        }
    
    }
    

    用法

    $encryptor = Encryptor::getInstance();
    
    $encryptedData = $encryptor->encrypt('Encrypt me please!');
    var_dump($encryptedData);
    
    $decryptedData = $encryptor->decrypt($encryptedData);
    var_dump($decryptedData);
    

    【讨论】:

    • 这仍然容易受到选择密文攻击。
    【解决方案3】:

    这也发生在我身上。我得到了类似的错误

    openssl_decrypt(): IV passed is only 14 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0
    

    我使用的是像openssl_cipher_iv_length('aes-128-cbc')这样的小写方法

    小写 aes-- 方法给出长度在 12 到 16 之间变化的结果。参考:https://www.php.net/manual/en/function.openssl-cipher-iv-length.php

    将方法设为大写 ​​openssl_cipher_iv_length('AES-128-CBC') 将给出一致的值,即 16。

    因此,在加密和解密时,IV 长度保持与 16 相同。

    【讨论】:

      猜你喜欢
      • 2019-01-13
      • 2023-03-25
      • 2020-04-28
      • 1970-01-01
      • 1970-01-01
      • 2019-11-03
      • 2021-04-21
      • 2019-09-30
      相关资源
      最近更新 更多