首页 > 解决方案 > 如何使用 PHP 加密和解密超过 65535 个字符的字符串

问题描述

这是我的问题,

我想加密在某些情况下可能很长的 JSON 文件。(有时包含 Base64 格式的图像)。

在以下测试服务器上,一切正常:

另一方面,在以下服务器上,(打算使用..)加密无法使用超过 65535 个字符,服务器似乎崩溃了。

CPU有限制吗?

php.ini 的参数会影响吗?

我在多台服务器上测试了完全相同的代码,在提到的两个 Synology 上,它都不起作用......

这是我的加密/解密类:

class PHP_AES_Cipher {

    private static $OPENSSL_CIPHER_NAME = "AES-256-CBC"; //Name of OpenSSL Cipher 
    private static $CIPHER_KEY_LEN = 32; 

    static function encrypt($key, $iv, $data) {
        if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0");
        } else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN); 
        }

        $encodedEncryptedData = base64_encode(openssl_encrypt($data, PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, $iv));
        $encodedIV = base64_encode($iv);
        $encryptedPayload = $encodedEncryptedData.":".$encodedIV;

        return $encryptedPayload;

    }


    static function decrypt($key, $data) {
        if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0");
        } else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN);
        }

        $parts = explode(':', $data); //Separate Encrypted data from iv.
        $decryptedData = openssl_decrypt(base64_decode($parts[0]), PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, base64_decode($parts[1]));

        return $decryptedData;
    }
}

我这样使用它:

$data = PHP_AES_Cipher::encrypt($key, $iv, $data);

$data = PHP_AES_Cipher::decrypt($key, $iv, $data);

假设一切都在某些服务器上运行,我认为代码没有问题。我已经检查了 Apache 和 PHP 日志,没有什么可报告的。

我一直在寻找几天不了解问题的原因。

希望有人可以帮助我:-)

标签: phpencryptionopensslphp-opensslsynology

解决方案


切块,

这就是我所做的(使用 PHPSecLib2 )

/**
 * AES encrypt large files using streams and chunking
 * 
 * @param resource $stream
 * @param resource $outputStream
 * @param string $key
 * @throws SecExecption
 */
function streamSymEncode($stream, &$outputStream, $key, $chunkSize = 10240){
    if(!is_resource($stream)) throw new Execption('Resource expected[input]');  
    rewind($stream); //make sure the stream is rewound

    if(!is_resource($outputStream)) throw new Execption('Resource expected[output]');

    $Cipher = new AES(AES::MODE_CBC);
    $Cipher->setKey($key);
    //create the IV
    $iv = Random::string($Cipher->getBlockLength() >> 3);
    $Cipher->setIV($iv);

    if(strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) throw new Execption('IV lenght check fail');

    fwrite($outputStream, $iv_base64.'$'); //add the IV for later use when we decrypt

    while(!feof($stream)){
        $chunk = fread($stream, $chunkSize); 
        fwrite($outputStream, rtrim(base64_encode($Cipher->encrypt($chunk)),'=').':');
    }

    $stat = fstat($outputStream);

    ftruncate($outputStream, $stat['size'] - 1);    //trim off the last character, hanging ':'    
}

/**
 * AES decrypt large files that were previously encrypted using streams and chunking 
 * 
 * @param resource $stream
 * @param resource $outputStream
 * @param string $key
 * @throws SecExecption
 */
function streamSymDecode($stream, &$outputStream, $key){
    if(!is_resource($stream)) throw new Execption('Resource expected[input]');
    rewind($stream); //make sure the stream is rewound

    if(!is_resource($outputStream)) throw new Execption('Resource expected[output]');

    $Cipher = new AES(AES::MODE_CBC);
    $Cipher->setKey($key);

    $iv = base64_decode(fread($stream, 22) . '==');
    $Cipher->setIV($iv);

    fread($stream, 1); //advance 1 for the $

    $readLine = function(&$stream){
        $line = '';
        while(false !== ($char = fgetc($stream))){
            if($char == ':') break;
            $line .= $char;
        }
        return $line;
    };

    while(!feof($stream)){
        $chunk = $readLine($stream);

        $decrypted = $Cipher->decrypt(base64_decode($chunk.'=='));
        if(!$decrypted) throw new Execption('Failed to decode!');

        fwrite($outputStream, $decrypted);
    }       
}

它需要两个文件流资源,例如您从中获得的内容fopen和一个密钥。然后它使用相同的加密,但将文件分块,$chunkSize将它们分开:,当它解码时,它将它分成块并重新组装所有内容。

它像这样结束(例如)

  IV$firstChunk:secondChunk:thirdChunk

这样您就不会在尝试加密大文件时耗尽内存。

请注意,这是我使用的较大类的一部分,所以我必须修剪一些东西并进行一些更改,我还没有测试过。

https://github.com/phpseclib/phpseclib

干杯。


推荐阅读