Reputation: 1933
I am using CakePHP's Security::rijndael()
function to encrypt and decrypt text and files. I previously wrote some code using mcrypt
directly, which worked in the same way, but then I found Security::rijndael
and realised I had reinvented the wheel. So the problem I have happens either way.
If I encrypt a string, or a text file, or a PDF document, the code below works perfectly and I get the correct decrypted string/file. However, if I try encrypting a .doc, .docx or an image file, the decrypted file is garbled.
Here's the code that does the encrypting/decrypting
public static function encrypt($plainText, $key) {
$plainText = base64_encode($plainText);
//Hash key to ensure it is long enough
$hashedKey = Security::hash($key);
$cipherText = Security::rijndael($plainText, $hashedKey, 'encrypt');
return base64_encode($cipherText);
}
public static function decrypt($cipherText, $key) {
$cipherText = base64_decode($cipherText);
$hashedKey = Security::hash($key);
$plainText = Security::rijndael($cipherText, $hashedKey, 'decrypt');
return base64_decode($plainText);
}
...and this code actually presents the file to the user (I've edited the code to keep it simple):
public function download($id){
App::uses('File', 'Utility');
$key = $this->User->getDocumentKey($id);
$file = new File('my_encrypted_file.docx');
$encrypted = $file->read();
$decrypted = Encrypt::decrypt($encrypted, $key);
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Content-Disposition: attachment; filename="my_decrypted_file.docx"');
echo $decrypted;
die();
}
Update - it appears that the encryption is a red herring, as the file is garbled even without encrypting and decrypting it! The following produces exactly the same broken file:
header('Content-Disposition: attachment; filename="test.docx"');
$file = new File($this->data['Model']['file']['tmp_name']);
echo $file->read();
die();
Upvotes: 0
Views: 1066
Reputation: 2729
I think I know the reason for that problem now, it is line 208
in Security.php
:
$out .= rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
Since PHP's mycrypt()
uses ZeroBytePadding
this line removes the padding afterwards.
The problem is that a .docx
-File (as far as I could check it) terminates with a few Null
-characters. If you only remove a single one of them, Word fails to open the file.
So what happens is that rtrim()
also deletes these bytes even though they are not part of the padding.
To fix this, you can add a termination character (for example X
) at the end of your files before encrypting and remove it after decrypting. This will prevent cutting off the tailing zero-bytes from the .docx
-files:
public static function encrypt($plainText, $key) {
$plainText = base64_encode($plainText . "X"); // `X` terminates the file
/* do encryption */
}
public static function decrypt($cipherText, $key) {
/* do decrytion */
return rtrim(base64_decode($plainText), "X"); // cut off the termination `X`
}
Upvotes: 0
Reputation: 1933
Well, I was barking up the wrong tree.
For whatever reason (whitespace at the start of some PHP file maybe?), adding ob_clean();
immediately after sending the headers, has fixed the problem.
Upvotes: 0