Signature
md5_file(string $filename, bool $binary = false): string|falsePurpose
md5_file() reads the file named by its first argument and returns the MD5 digest of the file's contents. Typical uses are quick change detection, cache keys for generated assets, and comparing a file with a checksum published by a trusted source for non-security purposes. Only the contents are hashed. The file name, modification time and permissions do not affect the result.
It behaves like calling md5() on the full contents, but you do not have to load the file into a PHP string yourself.
Signature
Parameters and defaults
$filename: the path or stream URL to read. Any stream wrapper that supports reading can be used, not just local paths.$binary: defaults tofalse. Withfalse, you get a 32-character lowercase hexadecimal string. Withtrue, you get the raw digest as a 16-byte binary string.
Return value and failure
On success you get a string: 32 hex characters, or 16 raw bytes when $binary is true. If the file cannot be opened, the function emits a warning and returns false. Missing files, unreadable files and wrapper failures all end up here.
Because the result is string|false, always compare with === false before using the digest.
A note about the examples
A real call usually looks like md5_file('/path/to/archive.zip'). To keep the examples below self-contained and independent of the disk, each one registers a tiny read-only stream wrapper called mem://. md5_file() reads through it exactly as it would read any other stream. In your own code you would just pass a normal path.
Example 1: hex digest, including an empty file
The hex digest of hello is well known, and an empty file has a digest too. An empty file is not an error.
class MemoryFile {
public $context;
public static array $files = [];
private string $data = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
if (!isset(self::$files[$path])) {
return false;
}
$this->data = self::$files[$path];
$this->pos = 0;
return true;
}
public function stream_read(int $count): string|false {
$chunk = substr($this->data, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_eof(): bool {
return $this->pos >= strlen($this->data);
}
public function stream_stat(): array|false {
return ['size' => strlen($this->data)];
}
}
stream_wrapper_register('mem', MemoryFile::class);
MemoryFile::$files['mem://hello.txt'] = 'hello';
MemoryFile::$files['mem://empty.txt'] = '';
echo json_encode([
'hello' => md5_file('mem://hello.txt'),
'empty' => md5_file('mem://empty.txt'),
]);{"hello":"5d41402abc4b2a76b9719d911017c592","empty":"d41d8cd98f00b204e9800998ecf8427e"}Example 2: raw binary output
With $binary set to true the result is always 16 bytes. Raw bytes are not printable, so convert them with bin2hex() if you want to display or log them.
class MemoryFile {
public $context;
public static array $files = [];
private string $data = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
if (!isset(self::$files[$path])) {
return false;
}
$this->data = self::$files[$path];
$this->pos = 0;
return true;
}
public function stream_read(int $count): string|false {
$chunk = substr($this->data, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_eof(): bool {
return $this->pos >= strlen($this->data);
}
public function stream_stat(): array|false {
return ['size' => strlen($this->data)];
}
}
stream_wrapper_register('mem', MemoryFile::class);
MemoryFile::$files['mem://abc.txt'] = 'abc';
$raw = md5_file('mem://abc.txt', true);
echo json_encode([strlen($raw), bin2hex($raw)]);[16,"900150983cd24fb0d6963f7d28e17f72"]
Example 3: matching md5() and handling a missing file
The digest of a file equals md5() of the same bytes. A file that cannot be opened gives false. The @ operator is used here only to keep the demo output clean. In production code, check the return value and log the problem instead of hiding warnings.
class MemoryFile {
public $context;
public static array $files = [];
private string $data = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
if (!isset(self::$files[$path])) {
return false;
}
$this->data = self::$files[$path];
$this->pos = 0;
return true;
}
public function stream_read(int $count): string|false {
$chunk = substr($this->data, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_eof(): bool {
return $this->pos >= strlen($this->data);
}
public function stream_stat(): array|false {
return ['size' => strlen($this->data)];
}
}
stream_wrapper_register('mem', MemoryFile::class);
$text = 'The quick brown fox jumps over the lazy dog';
MemoryFile::$files['mem://fox.txt'] = $text;
echo json_encode([
'same' => md5_file('mem://fox.txt') === md5($text),
'missing' => @md5_file('mem://missing.txt'),
]);{"same":true,"missing":false}Edge cases
- An empty file produces the digest
d41d8cd98f00b204e9800998ecf8427e, notfalse. - A path that points to a directory, or to something PHP cannot read, fails and returns
false. - Identical contents give identical digests regardless of file name.
- Even a one-byte difference changes the digest completely, so a digest is useful only for equality checks, not for measuring how similar two files are.
- Text mode does not exist here. Line endings are hashed as stored, so a file with
\r\nendings differs from the same text with\n.
Common mistakes
- Treating the result as always a string. A falsy or
falseresult from a missing file may be stored as a cache key or compared loosely. Use=== false. - Using MD5 for security. MD5 is not collision resistant, so it is unsuitable for signatures, password storage or defending against a malicious file author. Use
hash_file()with an algorithm such assha256instead. - Printing the binary form. With
$binary = true, the output is raw bytes. Display it withbin2hex(). - Hashing user-supplied paths. Passing unvalidated input lets a caller probe files or stream wrappers. Validate or map names to known locations first.
- Comparing digests with different case or format. The hex form is lowercase. Normalize external checksums before comparing.
Related functions
md5()hashes a string already in memory.hash_file()hashes a file with a chosen algorithm.hash_init()starts an incremental hashing context for data that arrives in pieces.
Recap
md5_file() returns the MD5 digest of a file's contents as 32 hex characters, or 16 raw bytes with $binary = true. It returns false with a warning when the file cannot be read. It is fine for checksums and cache keys, but use hash_file() with a stronger algorithm when security matters.
Tested examples
class MemoryFile {
public $context;
public static array $files = [];
private string $data = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
if (!isset(self::$files[$path])) {
return false;
}
$this->data = self::$files[$path];
$this->pos = 0;
return true;
}
public function stream_read(int $count): string|false {
$chunk = substr($this->data, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_eof(): bool {
return $this->pos >= strlen($this->data);
}
public function stream_stat(): array|false {
return ['size' => strlen($this->data)];
}
}
stream_wrapper_register('mem', MemoryFile::class);
MemoryFile::$files['mem://hello.txt'] = 'hello';
MemoryFile::$files['mem://empty.txt'] = '';
echo json_encode([
'hello' => md5_file('mem://hello.txt'),
'empty' => md5_file('mem://empty.txt'),
]);{"hello":"5d41402abc4b2a76b9719d911017c592","empty":"d41d8cd98f00b204e9800998ecf8427e"}
class MemoryFile {
public $context;
public static array $files = [];
private string $data = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
if (!isset(self::$files[$path])) {
return false;
}
$this->data = self::$files[$path];
$this->pos = 0;
return true;
}
public function stream_read(int $count): string|false {
$chunk = substr($this->data, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_eof(): bool {
return $this->pos >= strlen($this->data);
}
public function stream_stat(): array|false {
return ['size' => strlen($this->data)];
}
}
stream_wrapper_register('mem', MemoryFile::class);
MemoryFile::$files['mem://abc.txt'] = 'abc';
$raw = md5_file('mem://abc.txt', true);
echo json_encode([strlen($raw), bin2hex($raw)]);[16,"900150983cd24fb0d6963f7d28e17f72"]
class MemoryFile {
public $context;
public static array $files = [];
private string $data = '';
private int $pos = 0;
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
if (!isset(self::$files[$path])) {
return false;
}
$this->data = self::$files[$path];
$this->pos = 0;
return true;
}
public function stream_read(int $count): string|false {
$chunk = substr($this->data, $this->pos, $count);
$this->pos += strlen($chunk);
return $chunk;
}
public function stream_eof(): bool {
return $this->pos >= strlen($this->data);
}
public function stream_stat(): array|false {
return ['size' => strlen($this->data)];
}
}
stream_wrapper_register('mem', MemoryFile::class);
$text = 'The quick brown fox jumps over the lazy dog';
MemoryFile::$files['mem://fox.txt'] = $text;
echo json_encode([
'same' => md5_file('mem://fox.txt') === md5($text),
'missing' => @md5_file('mem://missing.txt'),
]);{"same":true,"missing":false}