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.

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!
The comment input hacked my text – that should read “slash zero slash four” and not just “slash four” on the rtrim.