Хеширование файлов в PHP: от простых вызовов до сложных сценариев

Раздел: Ввод-вывод в 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.

Типичная ошибка: попытка хешировать несуществующий файл или файл без прав доступа. Рекомендуется проверять существование файла через file_exists() или is_readable() перед вызовом hash_file(). Для больших файлов (несколько гигабайт) может потребоваться увеличить время выполнения скрипта (set_time_limit(0)).

Как получить хеш файла, гарантируя минимальное потребление памяти?

Использование 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() для создания нескольких контекстов. Это полезно при параллельном хешировании.

Ошибка: забыть закрыть файловый дескриптор (fclose) или использовать неправильный режим (не 'rb' для бинарного чтения). На Windows без 'b' символы новой строки могут быть преобразованы, что изменит хеш.

Как вычислить хеш файла с использованием внешней команды (например, 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.

Потенциальные проблемы: отсутствие утилиты, разные форматы вывода, возможность инъекции команд при передаче пути (нужно экранировать через escapeshellarg).
Как получить хеш нескольких файлов для проверки неизменности директории?

Для отслеживания изменений в папке можно создать массив хешей всех файлов. Вместо хеширования каждого по отдельности, иногда хешируют содержимое всего каталога через рекурсивный обход.

$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() для подписанных хешей.

Риск: при хешировании больших файлов в цикле легко превысить лимит времени. Стоит добавить set_time_limit(0) или обрабатывать файлы в фоновом режиме.
Как хешировать поток ввода (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://input доступен только для чтения один раз. Если его уже прочитали (например, в какой-то переменной), хеш будет пустым. Необходимо считывать сразу.

Расширенные примеры хеширования файлов

Ниже приведены нестандартные сценарии использования хеширования в 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...

Такая тактика экономит время для небольших файлов, не жертвуя безопасностью для крупных.

Хеширование файла в PHP - comments

En
Php file hash (php)