Will
Will

Reputation: 1933

Encrypting/decrypting some file types with Rijndael 256 (CakePHP Security library) garbles contents

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

Answers (2)

i_turo
i_turo

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

Will
Will

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

Related Questions