7

I'm developing a project in php where it's needed to encrypt files uploaded by users. This files could be from 1mb to 200mb more or less. Searching on the web, I came to the conclusion that the best way to do it was dividing files in chunks of, example, 4096 bytes. So I encrypt every chunk and append it to the full encrypted file. I'm actually using mcrypt and AES-256 encryption in CBC mode.

So, my questions are: 1) I have to create a new initial vector for every chunk, or can I get the last 16bytes of the last block of a previous chunk as the initial vector of the first block of the current chunk? This will result in having just one iv to append at the beginning of the encrypted file, and not one iv for every chunk to append before the encrpyted chunk.

2) In order to add a HMAC authentication. This question is linked to the previous one. Should I add it for the whole file or individually for every chunk. In this case, doing it for the whole file is a problem since it is usually added at the beggining of the file, and I cannot calculate the hmac until the encrypted file is complete.

3) Related to this. For file download, is it a good idea to decrypt (in chunks) and send file to the user simultaneously or is it better to decrypt first and send later?

Thanks

Sam Lowry
  • 71
  • 1
  • 2
  • 1
    Why reinvent the wheel? Why not use an already existing solution? – Mr. Llama Apr 23 '13 at 17:11
  • 3
    It would be great but I can't find a solution for this. Do you have any idea of one? Thanks! – Sam Lowry Apr 23 '13 at 17:14
  • Why encrypt in chunks? You don't need to provide the full file in one call to encrypt it as one file. – Ebbe M. Pedersen Apr 23 '13 at 17:15
  • But if I use mcrypt with the whole content of a big file, server run out of memory. That's why I encrypt it in chunks of 4096 bytes. But as I say in question 1, I can do it as one file, carrying the iv from one chunk to another. It is really the hmac part where I have trouble to do it as one file. – Sam Lowry Apr 23 '13 at 17:24

2 Answers2

5

You should encrypt the file stream and let PHP handle everything. In particular encryption filters combined with stream_filter_append to do what you want. Then you would just read chunks of the plaintext file and write them to the output file stream. The filter causes the encryption to happen.

This way you aren't reinventing the wheel and are using code that has likely been audited for security issues.

For hmac, most libraries let you keep adding data to the hmac until you call finalize or something like that. Then you would read in chunks of ciphertext, add them to the hmac. Repeat until the entire ciphertext has been added to the hmac and finalize it.

Or, install openssl on the server and call openssl functions from within PHP. You could use an authenticated cipher mode, etc.

mikeazo
  • 389
  • 2
  • 24
0

I had almost identical problem. Here's the solution I found out.

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

?>

Usage:

<?php
    $key = '3da541559918a808c2402bba5012f6c60b27661c'; // Your encryption key
    $filecrypt->encryptFileChunks('I-still-loooove-hula-hoop.gif', 'encrypted.gif', $key);
    $filecrypt->decryptFileChunks('encrypted.gif', 'decrypted.gif', $key);
?>
ND_
  • 63
  • 6
  • 4
    ECB mode? Very dangerous. No one should use this code as is. – mikeazo Apr 21 '14 at 18:18
  • 3
    Here's the reason why you don't want to use ECB mode: http://crypto.stackexchange.com/a/20946/5299 (in some cases, the file can be decryped by eyesight!!) – Suzanne Soy Sep 23 '16 at 17:50