事情是这样的。这是来自记忆且未经测试的,因为我的笔记本电脑上没有 PHPSecLib 库,而且我懒得将其全部设置......
require __DIR__ . '/vendor/autoload.php';
use phpseclib\Crypt\AES;
use phpseclib\Crypt\Random;
AESStreamEncode($input, $output, $key)
{
$cipher = new AES(AES::MODE_CBC);
$cipher->setKey($key);
$iv = Random::string($cipher->getBlockLength() >> 3);
$cipher->setIV($iv);
$base64_iv = rtrim(base64_encode($iv), '='); //22 chars
fwrite($output, $base64_iv); //store the IV this is like a salt
while(!feof($input)) {
$contents = fread($input, 1000000); //number of bytes to encrypt
$encrypted = $cipher->encrypt($contents);
//trim the = or ==, and replace with :, write to output stream.
fwrite($output, rtrim(base64_encode($encrypted), '=').':');
}
}
AESStreamDecode($input, $output, $key)
{
$cipher = new AES(AES::MODE_CBC);
$cipher->setKey($key);
$buffer = '';
$iv = false;
while(!feof($input)) {
$char = fgetc($input); //get a single char
if($char ==':'){
if(!$iv){
$iv = base64_decode(substr($buffer, 0, 22).'='); //iv is the first 22 of the first chunk.
$cipher->setIV($iv);
$buffer = substr($buffer, 22); //remove the iv
}
$buffer = base64_decode($buffer.'='); //decode base64 to bin
$decrypted = $cipher->decrypt($buffer);
fwrite($output, $decrypted);
$buffer = ''; //clear buffer.
}else{
$buffer .= $char;
}
}
}
Where $input
and $output
是有效的资源流句柄,例如 fromfopen
etc.
$input = fopen($filepath, 'r');
$output = fopen($ohter_filepath, 'w');
AESStreamEncode($input, $output, $key);
这可以让你使用类似的东西php://output
如果下载解密文件则作为流。
你必须删除=
因为有时会缺少或其中两个,所以我们不能依赖它们作为分隔符。我通常只是把 1 放回去,它总是能正确解码。无论如何,我认为这只是一些填充。
参考
GitHub 上的 PHPSecLib
PHPSecLib 示例
加密的文件应如下所示:
xUg8L3AatsbvsGUaHLg6uYUDIpqv0xnZsimumv7j:zBzWUn3xqBt+k1XP0KmWoU8lyfFh1ege:nJzxnYF51VeMRZEeQDRl8:
但有更长的块。 IV 就像盐一样,通常的做法是将其添加到加密字符串的前面或后面。例如
[xUg8L3AatsbvsGU]aHLg6uYUDIpqv0xnZsimumv7j:
中的部分[]
是IV,(在base64_encode之后有22个字符)我数了很多次,结果总是那么长。我们只需要记录IV并设置一次即可。我想你可以为每个块做不同的 IV,但无论如何。
如果您确实使用 PHPSecLib,它也有一些不错的 sFTP 东西。只要确保获得 2.0 版本即可。基本上它有一些针对不同加密算法的后备和本机 PHP 实现。所以喜欢它会尝试open_ssl
那么如果你错过了它,它将使用他们的本机实现。我将它用于 sFTP,所以我已经可以使用它了。 sFTP 需要扩展ssh2_sftp
如果我记得我们设置时它仅在 Linux 上可用。
UPDATE
对于下载,您只需发出标头,然后为解码函数提供output stream
,像这样的
$input = fopen('encrypted_file.txt', 'r');
$output = fopen('php://output', 'w');
header('Content-Type: "text/plain"');
header('Content-Disposition: attachment; filename="decoded.txt"');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0, max-age=0');
header("Content-Transfer-Encoding: binary");
header('Pragma: public');
//header('Content-Length: '.$fileSize); //unknown
AESStreamDecode($input, $output, $key);
这些是非常标准的标题。唯一真正的问题是,因为加密时文件大小不同,您不能简单地获取文件大小并使用它,因为它会更大一些。不传递文件大小不会阻止下载,只是不会有估计时间等。
但是因为我们在加密之前知道其大小,所以我们可以将其嵌入到文件数据本身中,如下所示:
3555543|xUg8L3AatsbvsGUaHLg6uYUDIpqv0xnZsimumv7j:zBzWUn3xqBt+k1XP0KmWoU8lyfFh1ege:nJzxnYF51VeMRZEeQDRl8:
然后在我们下载时将其拉出,但是您必须使用单独的函数来获取它,并且不搞乱解码文件可能有点棘手。
老实说,我认为这比它的价值更麻烦。
UPDATE2
不管怎样,我对嵌入文件大小进行了这些更改,这是一个选项,但如果不小心完成,它也可能会扰乱文件的解密。 (我没有测试过这个)
AESStreamEncode($input, $output, $key, $filesize = false)
{
$cipher = new AES(AES::MODE_CBC);
$cipher->setKey($key);
$iv = Random::string($cipher->getBlockLength() >> 3);
$cipher->setIV($iv);
$base64_iv = rtrim(base64_encode($iv), '='); //22 chars
//Option1 - optional filesize
if(false !== $filesize){
//add filesize if given in the arguments
fwrite($output, $filesize.'|');
}
/*
//Option2: using fstat, remove '$filesize = false' from the arguments
$stat = fstat($input);
fwrite($output, $stat['size'].'|');
*/
fwrite($output, $base64_iv); //store the IV this is like a salt
while(!feof($input)) {
$contents = fread($input, 1000000); //number of bytes to encrypt
$encrypted = $cipher->encrypt($contents);
//trim the = or ==, and replace with :, write to output stream.
fwrite($output, rtrim(base64_encode($encrypted), '=').':');
}
}
所以现在我们应该有文件大小3045345|asdaeASE:AEREA
等等,然后我们在解密的时候就可以把它拉出来了。
AESStreamDecode($input, $output, $key)
{
$cipher = new AES(AES::MODE_CBC);
$cipher->setKey($key);
$buffer = '';
$iv = false;
$filesize = null;
while(!feof($input)) {
$char = fgetc($input); //get a single char
if($char =='|'){
/*
get the filesize from the file,
this is a fallback method, so it wont affect the file if
we don't pull it out with the other function (see below)
*/
$filesize = $buffer;
$buffer = '';
}elseif($char ==':'){
if(!$iv){
$iv = base64_decode(substr($buffer, 0, 22).'='); //iv is the first 22 of the first chunk.
$cipher->setIV($iv);
$buffer = substr($buffer, 22); //remove the iv
}
$buffer = base64_decode($buffer.'='); //decode base64 to bin
$decrypted = $cipher->decrypt($buffer);
fwrite($output, $decrypted);
$buffer = ''; //clear buffer.
}else{
$buffer .= $char;
}
}
//when we do a download we don't want to wait for this
return $filesize;
}
解码获取文件大小部分充当后备,或者如果您不需要它,那么您不必担心它在解码时弄乱文件。下载时我们可以使用下面的函数,这样我们就不必等待文件完全读取来获取大小(这与我们上面所做的基本相同)。
//We have to use a separate function because
//we can't wait tell reading is complete to
//return the filesize, it defeats the purpose
AESStreamGetSize($input){
$buffer = '';
//PHP_INT_MAX (maximum allowed integer) is 19 chars long
//so by putting a limit of 20 in we can short cut reading
//if we can't find the filesize
$limit = 20;
$i; //simple counter.
while(!feof($input)) {
$char = fgetc($input); //get a single char
if($char =='|'){
return $buffer;
}elseif($i >= $limit){
break;
}
$buffer .= $char;
++$i; //increment how many chars we have read
}
return false;
}
然后在下载时您只需要进行一些更改即可。
$input = fopen('encrypted_file.txt', 'r');
//output streams dumps it directly to output, lets us handle larger files
$output = fopen('php://output', 'w');
//other headers go here
if(false !== ($filesize = AESStreamGetSize($input))){
header('Content-Length: '.$fileSize); //unknown
//because it's a file pointer we can take advantage of that
//and the decode function will start where the getSize left off.
// or you could rewind it because of the fallback we have.
AESStreamDecode($input, $output, $key);
}else{
//if we can't find the filesize, then we can fallback to download without it
//in this case we need to rewind the file
rewind($input);
AESStreamDecode($input, $output, $key);
}
如果你想缩短它,你也可以这样做,最多只有大约 19 个字符,所以这不是一个大的性能问题。
if(false !== ($filesize = AESStreamGetSize($input))) header('Content-Length: '.$fileSize);
rewind($input);
AESStreamDecode($input, $output, $key);
基本上上面,我们只是做文件大小标头(或不做),然后倒回并进行下载。它会重新读取文件大小,但这非常简单。
以供参考fstat(),希望这是有道理的。