Хеширование файлов в PHP: от простых вызовов до сложных сценариев
Эффективное хеширование файла в PHP: функция hash_file
Наиболее простой и производительный способ получить хеш-сумму файла в PHP - использовать встроенную функцию hash_file(). Она принимает алгоритм хеширования (например, 'sha256', 'md5', 'sha512') и путь к файлу, возвращая строку с хешем в шестнадцатеричном формате. Этот метод оптимален для файлов любого размера, так как PHP самостоятельно буферизирует чтение.
$hash = hash_file('sha256', '/path/to/file.txt');
echo $hash; // например, 'd7a8fbb3...'Php file hash (хеширование файла в php)
Для проверки целостности скачанного файла можно сравнить полученный хеш с эталонным значением. Если файл большой, функция не загружает его целиком в память, что предотвращает переполнение лимита memory_limit. Ошибка чаще всего связана с неверным путем или отсутствием прав на чтение. В таких случаях возвращается false.
Как получить хеш файла, гарантируя минимальное потребление памяти?
Использование hash_file() - стандартное решение для подавляющего большинства задач. Однако если нужно поддерживать прогресс хеширования или использовать собственные алгоритмы, потребуется более гибкий подход.
Как хешировать файл по частям для отслеживания прогресса или работы с потоками?
При необходимости контролировать процесс хеширования (например, отображать процент выполнения) применяется ручное чтение файла блоками. Функции hash_init(), hash_update(), hash_final() позволяют последовательно подавать данные. Это особенно актуально для очень больших файлов, когда хочется показывать пользователю индикатор загрузки.
$ctx = hash_init('sha256');
$handle = fopen('/path/to/bigfile.iso', 'rb');
while (!feof($handle)) {
$buffer = fread($handle, 8192);
hash_update($ctx, $buffer);
// здесь можно обновить прогресс
}
fclose($handle);
$hash = hash_final($ctx);
echo $hash;Проблема: если блоки слишком малы, увеличивается накладные расходы на вызовы функций. Рекомендуемый размер блока - 8–64 КБ. При использовании потоков (streams) можно применить hash_copy() для создания нескольких контекстов. Это полезно при параллельном хешировании.
Как вычислить хеш файла с использованием внешней команды (например, md5sum)?
В некоторых средах (shared hosting) запрещены функции работы с файлами, но разрешена exec(). Тогда можно вызвать системную утилиту для хеширования. Например, для md5 на Linux:
$hash = trim(exec('md5sum /path/to/file'));
// результат: 'd41d8cd98f... /path/to/file'
$parts = explode(' ', $hash);
$hashOnly = $parts[0];
echo $hashOnly;Такой подход может быть быстрее для больших файлов, если утилита написана на C. Однако он зависит от ОС и окружения. На Windows потребуется CertUtil или PowerShell.
Как получить хеш нескольких файлов для проверки неизменности директории?
Для отслеживания изменений в папке можно создать массив хешей всех файлов. Вместо хеширования каждого по отдельности, иногда хешируют содержимое всего каталога через рекурсивный обход.
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('/path/to/dir')
);
$hashes = [];
foreach ($files as $file) {
if ($file->isFile()) {
$hashes[$file->getPathname()] = hash_file('sha256', $file->getPathname());
}
}
// сравнение с предыдущим массивом
echo json_encode($hashes);Для больших каталогов (тысячи файлов) это может быть ресурсоемко. Альтернатива - хешировать только метаданные (размер, время изменения) или использовать hash_hmac_file() для подписанных хешей.
Как хешировать поток ввода (php://input) или stdout?
Функция hash_file() не работает с потоками. Для хеширования данных, поступающих из потока (например, при загрузке файла через POST), используют тот же принцип с hash_init/hash_update, читая из потока:
$ctx = hash_init('sha256');
$input = fopen('php://input', 'rb');
while (!feof($input)) {
$chunk = fread($input, 4096);
hash_update($ctx, $chunk);
}
fclose($input);
$hash = hash_final($ctx);
echo $hash;Это экономит память, так как не требует сохранения всего входящего потока во временный файл.
Расширенные примеры хеширования файлов
Ниже приведены нестандартные сценарии использования хеширования в PHP с подробными пояснениями и выводом результатов.
1. Хеширование с солью (солевое хеширование) для внешнего аудита
Иногда требуется добавить секретную соль к содержимому файла, чтобы предотвратить подделку хеша. Используется hash_hmac_file():
$salt = 'SuperSecretSalt42!';
$hash = hash_hmac_file('sha256', '/etc/passwd', $salt);
echo $hash;Результат (зависит от файла):
ec2a3e4f... (64 символа)
Внимание: соль не должна быть известна злоумышленнику, иначе HMAC теряет смысл.
2. Параллельное хеширование нескольких файлов с использованием потоков (pthreads или параллельного выполнения)
На серверах с поддержкой pthreads (или в CLI с parallel) можно ускорить хеширование. Пример с fork (только Unix):
$files = ['file1.iso', 'file2.iso', 'file3.iso'];
$pids = [];
foreach ($files as $f) {
$pid = pcntl_fork();
if ($pid == -1) {
die('fork fail');
} elseif ($pid) {
$pids[] = $pid;
} else {
$hash = hash_file('sha256', $f);
file_put_contents("/tmp/{$f}.hash", $hash);
exit(0);
}
}
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
}
echo 'Hashes saved in /tmp';Результат: три файла с хешами в /tmp. Этот подход требует установки pcntl и осторожности с ресурсами.
3. Хеширование с прогрессом для веб-интерфейса (через WebSocket или SSE)
В CLI можно выводить прогресс в консоль, в вебе - отправлять события. Пример с использованием hash_update и имитацией прогресса:
$file = '/path/to/largefile.dat';
$size = filesize($file);
$ctx = hash_init('sha256');
$handle = fopen($file, 'rb');
$processed = 0;
while (!feof($handle)) {
$chunk = fread($handle, 8192);
hash_update($ctx, $chunk);
$processed += strlen($chunk);
$percent = round($processed / $size * 100, 2);
// echo "Progress: $percent%\n";
}
fclose($handle);
$hash = hash_final($ctx);
echo "Hashing completed: $hash";Вывод:
Progress: 12.34% Progress: 25.68% ... Progress: 100.00% Hashing completed: b0d7c9...
Для веба нужно отправлять заголовки SSE или использовать long polling.
4. Хеширование содержимого только измененных блоков (дельта-хеширование)
Для файлов, которые часто обновляются частично (например, логи), вычисление полного хеша каждый раз неэффективно. Можно хешировать только изменившиеся сегменты. Простейший способ - разбить файл на блоки фиксированного размера и хранить их хеши. При последующей проверке хешировать только те блоки, чьи индексы изменились:
$blockSize = 4096;
$file = 'log.txt';
$blocks = [];
$handle = fopen($file, 'rb');
$index = 0;
while (!feof($handle)) {
$data = fread($handle, $blockSize);
if (strlen($data) > 0) {
$blocks[$index] = hash('sha256', $data);
$index++;
}
}
fclose($handle);
// Теперь можно проверить только блоки, чей индекс изменился с предыдущего замера
// Для полного хеша можно скомбинировать все хеши блоков в один: hash('sha256', implode('', $blocks));
echo 'Total blocks: ' . count($blocks);Результат:
Total blocks: 42
Недостаток: если размер блока не кратен размеру файла, последний блок придется дополнять нулями, иначе хеш будет разным. Этот метод подходит для файлов с регулярной структурой.
5. Хеширование с автоматическим выбором алгоритма по размеру файла
Для маленьких файлов подойдёт быстрый MD5, для больших - более стойкий SHA-512. Можно написать функцию с адаптацией:
function adaptiveHash(string $path): string {
$size = filesize($path);
if ($size < 1024 * 1024) { // < 1 MB
$algo = 'md5';
} elseif ($size < 100 * 1024 * 1024) { // < 100 MB
$algo = 'sha256';
} else {
$algo = 'sha512';
}
return hash_file($algo, $path);
}
echo adaptiveHash('small.txt'); // md5
echo adaptiveHash('medium.dat'); // sha256
echo adaptiveHash('huge.iso'); // sha512Результат (пример):
098f6bcd... d7a8fbb3... cfdf114e...
Такая тактика экономит время для небольших файлов, не жертвуя безопасностью для крупных.