Strings7 min readExamples run on PHP 8.5.10

md5()

Learn how PHP 8.4 md5() computes a 32-character hex digest or a 16-byte raw digest, where it is still useful, and why it must not hash passwords.

Signature

md5(string $string, bool $binary = false): string

Purpose

md5() turns a string of any length into a fixed-size fingerprint using the MD5 message-digest algorithm. The same input always gives the same digest, and even a tiny change in the input gives a completely different one. That makes it handy for non-security jobs such as building cache keys, labelling content for quick comparison, or matching checksums published by older systems.

It is not suitable for protecting passwords. MD5 is designed to be fast, which helps an attacker who tries billions of guesses. The manual warns against it for this purpose and points to password_hash() instead. MD5 is also unsuitable where an attacker could craft two different inputs with the same digest, such as signatures or integrity checks against a hostile party.

Signature

Parameters and defaults

  • $string is the data to hash. PHP strings are byte sequences, so the digest depends on the exact bytes, including the encoding of any non-ASCII text.
  • $binary defaults to false. When it is true, the digest comes back as raw binary instead of hexadecimal text.

Return value and failure cases

With the default $binary = false, you get a 32-character lowercase hexadecimal string. With $binary = true, you get a 16-byte string that is usually not printable.

The function has no "false on failure" result. It always returns a string. The only error situation is a type problem: passing a value that cannot be used as a string, such as an array, throws a TypeError. Under declare(strict_types=1), passing an integer where a string is expected is also rejected.

Example 1: hashing a known string

This mirrors the idea from the manual: the digest of apple is a well-known value.

Example
<?php
$str = 'apple';
echo md5($str), "\n";
Output
1f3870be274f6c49b3e31a0c6728957f

The output is the 32-character hexadecimal digest followed by a newline.

Example 2: the empty string and a short word

MD5 is defined for empty input too, so md5('') is valid and returns a normal 32-character digest.

Example
<?php
$hashes = [
    'empty' => md5(''),
    'hello' => md5('hello'),
];
echo json_encode($hashes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Output
{"empty":"d41d8cd98f00b204e9800998ecf8427e","hello":"5d41402abc4b2a76b9719d911017c592"}

Example 3: hexadecimal versus binary output

The binary form is the same 128-bit digest, only encoded differently. Use bin2hex() to convert it back to the text form.

Example
<?php
$raw = md5('apple', true);
$hex = md5('apple');
echo json_encode([strlen($raw), strlen($hex), bin2hex($raw) === $hex]);
Output
[16,32,true]

Edge cases

  • Empty string: it has a fixed, valid digest, so never treat a hash as "missing" just because the source was empty.
  • Encoding matters: the same visible text saved as UTF-8 and as another encoding is a different byte sequence and gets a different digest.
  • Binary output length: the raw result is always 16 bytes. Do not print it directly, store it in a text-only field or put it in JSON without encoding it first, because it can contain arbitrary bytes.
  • Type coercion: in default (non-strict) mode, a number such as 123 is converted to the string "123" before hashing. Be explicit with a cast when you want to make that obvious.
  • Case of the hex digits: PHP returns lowercase. If you compare against a checksum written in uppercase, normalise one side first.

Common mistakes

  1. Hashing passwords with md5(). Use password_hash() to store them and password_verify() to check them.
  2. Assuming a hash is encryption. A digest cannot be decrypted, and it does not hide short or guessable inputs from someone who simply hashes candidates.
  3. Comparing digests with == in security-sensitive code. Loose comparison of strings can behave surprisingly when both look like numbers. Prefer ===, or hash_equals() when comparing secret values to avoid timing leaks.
  4. Treating the digest as unique. Distinct inputs can in theory share a digest, and deliberate collisions are practical for MD5. Do not use it as a trusted identity for untrusted data.
  5. Forgetting $binary. Code that expects 16 bytes but receives 32 hex characters, or the reverse, will produce wrong keys or lengths.
  • hash() computes many other digests, including stronger algorithms such as SHA-256.
  • password_hash() and password_verify() are the right tools for passwords.
  • hash_equals() compares two strings in constant time.
  • sha1() and crc32() are other fingerprinting helpers with different sizes and purposes.
  • bin2hex() converts raw bytes, such as binary digests, to hexadecimal text.

Recap

md5() returns a deterministic 32-character hex digest of a string, or a 16-byte raw digest when $binary is true. It always returns a string and only throws for type errors. Use it for simple non-security fingerprints, choose hash() with a stronger algorithm when integrity matters, and never use it to store passwords.

Tested examples

Hashing a known string
<?php
$str = 'apple';
echo md5($str), "\n";
Output
1f3870be274f6c49b3e31a0c6728957f
The empty string and a short word
<?php
$hashes = [
    'empty' => md5(''),
    'hello' => md5('hello'),
];
echo json_encode($hashes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
Output
{"empty":"d41d8cd98f00b204e9800998ecf8427e","hello":"5d41402abc4b2a76b9719d911017c592"}
Hexadecimal versus binary output
<?php
$raw = md5('apple', true);
$hex = md5('apple');
echo json_encode([strlen($raw), strlen($hex), bin2hex($raw) === $hex]);
Output
[16,32,true]