Disclaimer: English isn't my mother tongue so feel free to ask if something isn't clear.
Hi there,
I have to encrypt files using AES as soon as they are uploaded on the server and send the key needed to decrypt them via mail to the client (not storing it anywhere server side). Files can be as big as 2GB and are deleted 7 days after their upload.
Here is what I'm using to encrypt/decrypt files :
function encrypt_file($source, $destination, $key) {
$iv = md5("\x1B\x3C\x58".$key, true);
$ivsize = openssl_cipher_iv_length('aes-256-cbc');
$fp = fopen($destination, 'wb') or die("Could not open file for writing.");
$handle = fopen($source, "rb");
while (!feof($handle)) {
$e = 0;
$contents = fread($handle, 4 * 1024 * 1024);
$ciphertext = openssl_encrypt($contents, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$iv = substr($ciphertext, -$ivsize);
while (!fwrite($fp, $ciphertext)) {
$e++;
if ($e == 5) {
die("Couldn't write to file.");
break 2;
}
}
}
fclose($handle);
fclose($fp);
}
function streamdecrypt_file($source, $key) {
$iv = md5("\x1B\x3C\x58".$key, true);
$ivsize = openssl_cipher_iv_length('aes-256-cbc');
$handle = fopen($source, "rb");
while (!feof($handle)) {
$contents = fread($handle, 4 * 1024 * 1024);
$raw = openssl_decrypt($contents, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$iv = substr($contents, -$ivsize);
print $raw; // Printing because it's directly sent to the user to download
}
fclose($handle);
}
If you're wondering why 4 * 1024 * 1024
it's just that this is the buffer size with which I got the fastest encryptions. My implementation uses the schema proposed here https://stackoverflow.com/a/30742644/3857024
I also made those 2 little functions to encrypt a string to a file using a passphrase :
function encrypt_string($source, $destination, $passphrase) {
$iv = md5("\x1B\x3C\x58".$passphrase, true);
$key = md5("\x2D\xFC\xD8".$passphrase, true);
$ciphertext = openssl_encrypt($source, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$fp = fopen($destination, 'wb') or die("Could not open file for writing.");
fwrite($fp, $ciphertext) or die("Could not write to file.");
fclose($fp);
}
function decrypt_string($source, $passphrase) {
$iv = md5("\x1B\x3C\x58".$passphrase, true);
$key = md5("\x2D\xFC\xD8".$passphrase, true);
$contents = file_get_contents($source);
return openssl_decrypt($contents, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
}
And here is what I'm finally doing when an upload is complete :
$skey = /* 32 chars random generated string [a-Z0-9]*/
$ukey = /* 10 chars random generated string [a-Z0-9]*/
encrypt_file($originalFile, $encryptedFile, $skey);
encrypt_string($skey, $encryptedKey, $ukey);
I then delete the original file and send a link containing the $ukey
to the user via mail.
When they want to decrypt the file to download it, I first decrypt the file containing the $skey
using the $ukey
, checking if I end up with a 32-chars 256-bits long string made of [a-Z0-9]. If the $skey
doesn't match the regexp, I know the $ukey
is invalid, so I stop there.
I did this so that I wouldn't have to decrypt the file to check if the key was correct or not.
Now I hope that my questions fit in SO :
- Am I doing it right ?
- Is there anything that could/should be improved ?
- It takes about 60s to encrypt a 2GB file, is that an "ok" result ?
- Is it good enough ? The goal is to prevent an attacker gaining access to the server to also gain access to the users files already stored. I know he would then be able to modify the code and access the following uploads, but that should protect the files already stored right ? Am I doing too much for nothing ?
Thank you for your answers !