Easy File Encryption

I’ve read in two different books that the streams extension of PHP is one of the most useful but least utilised parts of the language. I’ve always paid lip service to that idea but something I saw the other day really bought this home to me and I thought I’d write about it here.

I’m building an app where I need to encrypt files uploaded by a user to add an extra security layer. I was initially thinking of using stream_filter_register() to create my own stream filter as the files are read and written. If you’re not familiar with the concept of stream filters in PHP they’re a very powerful feature. By attaching a filter to a stream you can perform various operations to data as it is being read from or written to a stream. Once the filter is defined and attached to the stream this is done completely transparently. Anyway, coming back to my problem of encrypting files, I did a quick search on Google and there didn’t seem to be an easy way of doing this. I then came across this gem in the PHP manual. PHP has a number of built in stream filters and one of them is an encryption filter. Providing you have the mcrypt extension installed encrypting and decrypting files is as easy as registering a stream filter on a stream! Pasted below is the example code from the PHP manual.

Here’s how to encrypt data as it’s written to a file:


<?php

$passphrase = 'My secret';

/* Turn a human readable passphrase

* into a reproducable iv/key pair

*/

$iv = substr(md5('iv'.$passphrase, true), 0, 8);

$key = substr(md5('pass1'.$passphrase, true) .

md5('pass2'.$passphrase, true), 0, 24);

$opts = array('iv'=>$iv, 'key'=>$key);

$fp = fopen('secret-file.enc', 'wb');

stream_filter_append($fp, 'mcrypt.tripledes', STREAM_FILTER_WRITE, $opts);

fwrite($fp, 'Secret secret secret data');

fclose($fp);
?>

and here’s how to decrypt data read from a file:


<?php

$passphrase = 'My secret';

/* Turn a human readable passphrase

* into a reproducable iv/key pair

*/

$iv = substr(md5('iv'.$passphrase, true), 0, 8);

$key = substr(md5('pass1'.$passphrase, true) .

md5('pass2'.$passphrase, true), 0, 24);

$opts = array('iv'=>$iv, 'key'=>$key);

$fp = fopen('secret-file.enc', 'rb');

stream_filter_append($fp, 'mdecrypt.tripledes', STREAM_FILTER_READ, $opts);

$data = rtrim(stream_get_contents($fp));

fclose($fp);echo $data<code>;</code>

?>

It couldn’t be easier! The example in the manual uses tripledes but you can use any of the cyphers and modes available in mcrypt.

The next step for me is to put together an example for my application. I want to store the uploaded files in a database so I need to open the file for reading from its location in $_FILES, adding an encryption filter to the stream, and bind it as a LOB parameter to a PDO insert prepared statement. To read the information back out I need to bind the decryption stream filter to the stream returned from PDO on a select operation. Bearing in mind the issues that PDO/MySQL has with returning streams from LOB fields I’m hoping this should be fairly easy. Hopefully this post will help someone looking to do this task.

7 thoughts on “Easy File Encryption

  1. Keep in mind, the decrypted file will be padded at the end with \4 to match a block size. If dealing with Binary data you’ll probably want to only trim those characters to make sure you don’t accidentally mess with valid data.

    $data = rtrim(stream_get_contents($fp),”\4″);

    I turned this idea into an object that would allow me to encrypt a file to a destination file, decrypt to a destination file or decrypt to the browser with a minimum memory footprint. Instead of using get_contents, I read until EOF in 2048 byte blocks, doing the output as I go. When I hit the EOF I apply the rtrim to that specific block of data only before outputting it.

    Thanks for the great post!

      1. Hey Tony!

        Thanks for the correction! But there is one more thing to consider: What if the plain text contains \4 and (slash-four and slash-zero) bytes at the end? I’ve just ran into the problem that the the plain text is corrupted in these cases.

        The objective is to encrypt and decrypt files with *any* content (even if all null bytes) and get exactly the plain text after decrypting. Shouldn’t that be possible?

        @Jeremy: Thanks for the post!

          1. I could be off here, but couldn’t you pad the stream with you’re own unique set of bits as an identifier prior to encryption and then trim identifier + encrypt padding?

  2. Good article but maybe I’m missing something. You state that you needed to encrypt a file, “uploaded by a user” but in your code, you are only encrypting a string (“Secret secret secret data”). fwrite takes a string as input but what if I needed to encrypt a 500mb video file? I wouldn’t necessarily want to load that into memory through something like file_get_contents. Is there a way to read directly from a binary file on the file system using this method?

    1. Hey Brandon, I totally agree with you. The solution would be to use something like fgets() to read the file in chunks. That should avoid the issues you might have with taking the whole thing into memory.

Leave a Reply