0

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();
Will
  • 1,893
  • 4
  • 29
  • 42
  • `the decrypted file is garbled`: does that mean you just can't open it or do the bytes completely differ from what the should be? Possibly `rijndael()` leaves some padding or adds (or removes) a null-byte at the end of the decrypted data. – i_turo Aug 20 '14 at 13:16
  • The .docx, for example, starts with: PK##########!#M."��###�#######[Content_Types].xml. Not sure if that answers your question? – Will Aug 20 '14 at 13:17
  • That seems correct. (checked a valid .docx and it looked the same) Does it look different before encryption? What are the last few bytes of the file? – i_turo Aug 20 '14 at 13:21
  • Hmmm... if I open the file in vim the last few characters are ^@^@^@^@. Is that helpful? :S I will check the doc before encryption now – Will Aug 20 '14 at 13:42

2 Answers2

0

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`
}
i_turo
  • 2,679
  • 1
  • 13
  • 15
0

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.

Will
  • 1,893
  • 4
  • 29
  • 42