title: Word/Excel files corrupted when downloading from PHP
body: <p>I'm building a simple file upload/file download functionality into my database.  The only complicated part is that all files need to be encrypted using my fancy-shmancy encryption methods.</p>
  
  <p>So what I do is make an SQL entry that stores things like: id_file, filename, extension, size, dateadded, etc</p>
  
  <p>Then once I've got the id_file I take the file contents, encrypt them, then save the contents to my server as [id_file].txt.</p>
  
  <p>Then here's the code for downloading the file again:</p>
  
  <pre><code>header("Pragma: public");
  header('Content-Disposition: attachment;filename="'.$file['name'].'.'.$file['extension'].'"');
  header('Cache-Control: max-age=0');
  
  echo someFunctionIMadeForGettingAndDecryptingFileContents($_GET['id_file']);
  
  exit;
  </code></pre>
  
  <p>Really simple stuff and works PERFECTLY for all file types EXCEPT .docx and .xlsx. When downloading .docx or .xlsx files Office gives me an error saying "Word found unreadable content in "NAME OF FILE". Do you want to recover the contents of this document? If you trust the source... bla bla"  I then click 'Yes'.  It thinks a bit, and the file opens up just fine.  But obviously I can't have my clients using this if they're going to get that error every time.</p>
  
  <p>The code I've written works perfectly for all other file types.  Even .doc, .xls, and .zip files work fine.</p>
  
  <p>My first thought was to look at the headers.  I've tried all sorts of solutions like the ones listed here:</p>
  
  <p><a href="https://stackoverflow.com/questions/10145067/why-my-downloaded-file-is-alwayes-damaged-or-corrupted">why my downloaded file is alwayes damaged or corrupted?</a>
  <a href="https://stackoverflow.com/questions/9084011/php-downloading-excel-file-becomes-corrupt">PHP downloading excel file becomes corrupt</a></p>
  
  <p>Those didn't work.</p>
  
  <p>I know an issue can be with extra padding or white space being added to the file.  But if I upload a .txt file and then download it again... I can see that there isn't anything extra being added.</p>
  
  <p>If I MD5 the original file (good.docx) and the downloaded version of the original file (bad.docx), the hashes ARE different.</p>
  
  <p>If I change good.docx to good.zip and unzip the archive.  Then do the same for bad.docx.  Then MD5 both directories, the hashes are the SAME.  And I've hashed each file inside good.zip and bad.zip and each file hash is the same.</p>
  
  <p>Also to note, elsewhere on my server I use PHPWord and PHPExcel to generate Office files dynamically and those files all download great.  The headers/code I use for PHPExcel are:</p>
  
  <pre><code>header("Pragma: public");
  header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  header('Content-Disposition: attachment;filename="'.$filename.'.xlsx"');
  header('Cache-Control: max-age=0');
  $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
  $objWriter-&gt;save('php://output');
  exit;
  </code></pre>
  
  <p>(Yes, I've tried using the "Content-Type" header on my other code above but that didn't help.)</p>
  
  <p>I've also tried saving the file on my server, downloading it, and opening it.  I get the same error when going through that process.  Here is the code I used to do that:</p>
  
  <pre><code>$f=fopen("/myPath/temp.docx","w");
  fwrite($f,someFunctionIMadeForGettingAndDecryptingFileContents($_GET['id_file']));
  fclose($f);
  exit;
  </code></pre>
  
  <p>I've tried creating an empty Word file called "blank.docx".  Then made it so instead of the function save a new file.... it replaces the contents of blank.docx with the decrypted file contents.  But when downloading blank.docx after that process I get all the same... an error but it eventually opens.  None of the file properties (like Template: Normal.dotm) that were originally on blank.docx are there on the served modified blank.docx.</p>
  
  <p>I'm using Office 2007</p>
  
  <p><strong>UPDATE</strong></p>
  
  <p>Here is a link to download the good (original) version of a file: <a href="http://empowerdb.org/good.docx" rel="nofollow noreferrer">http://empowerdb.org/good.docx</a></p>
  
  <p>And here is a link to download the bad (processed) version of the file:
  <a href="http://empowerdb.org/bad.docx" rel="nofollow noreferrer">http://empowerdb.org/bad.docx</a></p>
  
  <p><strong>SOLUTION</strong></p>
  
  <p>As Mr. Llama pointed out below, my encryption function was lopping off some extra null bytes.  But it turned out the culprit wasn't as obvious as you'd think.  Here's my encryption:</p>
  
  <pre><code>trim(base64_encode(IV.mcrypt_encrypt(MCRYPT_RIJNDAEL_128,ENCKEY,$contents,MCRYPT_MODE_CBC,IV)))
  </code></pre>
  
  <p>The problem wasn't with trim() or with base64_encode().  It was with the mcrypt function.  The way I solved this was before passing my file contents to get encrypted I did another base64_encode().  So like this...</p>
  
  <pre><code>$file_contents_encrypted=base64_encode(myEncryptionFunction($file_contents));
  </code></pre>
  
  <p>And of course the reverse upon decryption.</p>
  
  <p>The base64_encode is technically being run twice.  But I can see how it's needed to be run in this case BEFORE mcrypt because of the unique zip like format of .docx and .xlsx</p>