Thanks for the comments and email leads... The solution is to use headers and readfile. I used David Walsh's header example http://davidwalsh.name/php-force-download and combined it with php's readfile. The readfile reads the file with the hash name and the headers saves it with another name. The original filename or location is never shown to the public. Here is some useful php code:
// get doc details
$stmt = $conn->prepare("SELECT * FROM docs WHERE docs_hash = :hash LIMIT 1 ");
$stmt->execute(array(':hash' => $docs_hash));
$result = $stmt;
if ($result->rowCount() > 0) {
foreach($result as $row) {
$docs_filename = $row['docs_filename'];
$docs_extension = $row['docs_extension'];
$docs_downloaded = $row['docs_downloaded'];
$download_file = "doc_" . $docs_hash . "." . $docs_extension;
}
// echo "File 1: $download_file <br>";
// echo "File 2: $docs_filename <br>";
// echo "Bucket: " . _AWS_BUCKET_ . "<br>";
// required for IE
if (ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off'); }
// get the file mime type using the file extension
switch(strtolower(substr(strrchr($docs_filename, '.'), 1))) {
case 'pdf': $mime = 'application/pdf'; break;
case 'zip': $mime = 'application/zip'; break;
case 'jpeg':
case 'jpg': $mime = 'image/jpg'; break;
default: $mime = 'application/force-download';
}
header('Pragma: public'); // required
header('Expires: 0'); // no cache
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Content-Type: ' . $mime);
header('Content-Disposition: attachment; filename="' . basename($docs_filename) . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: close');
readfile("https://s3.amazonaws.com/" . _AWS_BUCKET_ . "/" . $download_file);
exit();
}