Você faz o download utilizando a função header do PHP e todos seus atributos. Quando o arquivo chega, vem corrompido. A coisa fica mais confusa ainda porque a maioria dos formatos de arquivo funciona corretamente, mas os arquivos de imagem chegam corrompidos.

Tive esse problema e gostaria de compartilhar a solução encontrada:

Enviei um arquivo qualquer e comparei o tamanho. Vi que estava diferente (3 bytes a mais). Enviei um arquivo de texto e percebi que depois que abria utilizando o script, o arquivo de texto estava com uma linha a mais no início.

Procurei por todo o script e não achei nada. Quando fui em uma das includes encontrei o erro. No final da include, depois que fechava o PHP (?>), tinha uma linha a mais.

Pronto. Removi a linha e tudo funcionou perfeitamente :).

Isso acontece porque antes do comando header não pode ter um byte sequer. Ela indica o cabeçalho do arquivo, então deve ser a primeira coisa a ser carregada, ou então dá erro.