使用php加密大文件的最佳方法


Best approach to encrypt big files with php

我正在开发一个php项目,它需要加密用户上传的文件。这个文件的大小可以在1mb到200mb之间。在网上搜索后,我得出结论,最好的方法是将文件分成若干块,例如4096字节。所以我加密了每个数据块并将其附加到完整的加密文件中。我实际上在CBC模式下使用mcrypt和AES-256加密。

我的问题是:1)我必须为每个块创建一个新的初始向量,或者我可以将前一个块的最后一个块的最后16个字节作为当前块的第一个块的初始向量吗?这将导致在加密文件的开头只附加一个iv,而不是在加密块之前每个块都附加一个iv。

2)为了添加HMAC认证。这个问题与上一个问题有关。我应该为整个文件添加还是为每个块单独添加?在这种情况下,对整个文件执行此操作是一个问题,因为它通常是在文件的开头添加的,并且在加密文件完成之前我无法计算hmac。

3)与此相关。对于文件下载,解密(以块的形式)并同时发送文件给用户是一个好主意,还是先解密后发送更好?

谢谢

您应该加密文件流并让PHP处理所有内容。特别是加密过滤器与stream_filter_append相结合,可以做您想做的事情。然后,您只需读取明文文件的块并将它们写入输出文件流。过滤器导致加密发生。

这样你就不会重新发明轮子,并且使用的代码可能已经被审计了安全问题。

对于hmac,大多数库允许你不断向hmac添加数据,直到你调用finalize或类似的函数。然后,您将读取密文块,并将它们添加到hmac中。重复,直到整个密文已添加到hmac并完成。

或者,在服务器上安装openssl并从PHP内部调用openssl函数。您可以使用经过身份验证的密码模式,等等。

我遇到了几乎相同的问题。这是我找到的解决办法。

<?php
$filecrypt = new filecrypt();
class filecrypt{
    var $_CHUNK_SIZE;
    function __construct(){
        $this->_CHUNK_SIZE = 100*1024; // 100Kb
    }
    public function encrypt($string, $key){
        $key = pack('H*', $key);
        if (extension_loaded('mcrypt') === true) return mcrypt_encrypt(MCRYPT_BLOWFISH, substr($key, 0, mcrypt_get_key_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB)), $string, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB), MCRYPT_RAND));
        return false;
    }
    public function decrypt($string, $key){
        $key = pack('H*', $key);
        if (extension_loaded('mcrypt') === true) return mcrypt_decrypt(MCRYPT_BLOWFISH, substr($key, 0, mcrypt_get_key_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB)), $string, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB), MCRYPT_RAND));
        return false;
    }
    public function encryptFileChunks($source, $destination, $key){
        return $this->cryptFileChunks($source, $destination, $key, 'encrypt');
    }
    public function decryptFileChunks($source, $destination, $key){
        return $this->cryptFileChunks($source, $destination, $key, 'decrypt');
    }
    private function cryptFileChunks($source, $destination, $key, $op){
        if($op != "encrypt" and $op != "decrypt") return false;
        $buffer = '';
        $inHandle = fopen($source, 'rb');
        $outHandle = fopen($destination, 'wb+');
        if ($inHandle === false) return false;
        if ($outHandle === false) return false;
        while(!feof($inHandle)){
            $buffer = fread($inHandle, $this->_CHUNK_SIZE);
            if($op == "encrypt") $buffer = $this->encrypt($buffer, $key);
            elseif($op == "decrypt") $buffer = $this->decrypt($buffer, $key);
            fwrite($outHandle, $buffer);
        }
        fclose($inHandle);
        fclose($outHandle);
        return true;
    }
    public function printFileChunks($source, $key){
        $buffer = '';
        $inHandle = fopen($source, 'rb');
        if ($inHandle === false) return false;
        while(!feof($inHandle)){
            $buffer = fread($inHandle, $this->_CHUNK_SIZE);
            $buffer = $this->decrypt($buffer, $key);
            echo $buffer;
        }
        return fclose($inHandle);
    }
}
?>

用法:

<?php
    $key = '3da541559918a808c2402bba5012f6c60b27661c'; // Your encryption key
    $filecrypt->encryptFileChunks('I-still-loooove-hula-hoop.gif', 'encrypted.gif', $key);
    $filecrypt->decryptFileChunks('encrypted.gif', 'decrypted.gif', $key);
?>