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

Posted

技术标签:

【中文标题】用php加密大文件的最佳方法【英文标题】:Best approach to encrypt big files with php 【发布时间】:2013-04-17 00:14:53 【问题描述】:

我正在用 php 开发一个项目,需要对用户上传的文件进行加密。这些文件可能或多或少从 1mb 到 200mb。在网上搜索,我得出的结论是,最好的方法是将文件分割成块,例如 4096 字节。所以我加密每个块并将其附加到完整的加密文件中。我实际上是在 CBC 模式下使用 mcrypt 和 AES-256 加密。

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

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

3) 与此相关。对于文件下载,最好同时解密(分块)并将文件发送给用户,还是先解密再发送更好?

谢谢

【问题讨论】:

为什么要重新发明***?为什么不使用现有的解决方案? 这很好,但我找不到解决方案。你有什么想法吗?谢谢! 为什么要分块加密?您无需在一次调用中提供完整文件即可将其加密为一个文件。 但是如果我使用 mcrypt 处理一个大文件的全部内容,服务器内存不足。这就是我以 4096 字节为单位对其进行加密的原因。但正如我在问题 1 中所说,我可以将其作为一个文件,将 iv 从一个块传送到另一个块。这真的是 hmac 部分,我很难将它作为一个文件来完成。 【参考方案1】:

您应该加密文件流并让 PHP 处理所有事情。特别是 encryption filters 结合 stream_filter_append 做你想做的事。然后,您只需读取纯文本文件的块并将它们写入输出文件流。过滤器会导致加密发生。

这样您就不会重新发明***,而是使用可能已经过安全问题审核的代码。

对于 hmac,大多数库都允许您继续向 hmac 添加数据,直到您调用 finalize 或类似的东西。然后你会读入 ciphertext 块,将它们添加到 hmac。重复直到将整个密文添加到 hmac 并完成它。

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

【讨论】:

【参考方案2】:

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

<?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);
?>

【讨论】:

欧洲央行模式?非常危险。任何人都不应按原样使用此代码。 这就是你不想使用ECB模式的原因:crypto.stackexchange.com/a/20946/5299(在某些情况下,文件可以通过视力解密!!)

以上是关于用php加密大文件的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP 中获取大文件(> 2 GB)文件大小的最佳方法? [复制]

如何在Laravel中加密大文件?

怎样对 Python 源码加密

将大文件写入 S3 的最佳方法是啥?

在 Rails 应用程序中处理大文件上传的最佳方法是啥?

在 Python 中划分大文件以进行多处理的最佳方法是啥?