Hashing7 min readExamples run on PHP 8.5.10

hash()

Learn how PHP 8.4 hash() turns a string into a digest with a named algorithm, how hex and binary output differ, and which mistakes to avoid.

Signature

hash(string $algo, string $data, bool $binary = false, array $options = []): string
Available since PHP 5.1.27.48.08.18.28.38.48.5All versions →

Purpose

hash() computes a message digest of a string using an algorithm you choose by name. The same input and algorithm always give the same output, and a small change in the input produces a completely different digest. Typical uses are content fingerprints, cache keys, checksums and ETag-style identifiers.

A plain digest is not a way to store passwords. For passwords use password_hash() and password_verify(). For authenticating a message with a secret key use hash_hmac().

Signature

Parameters and defaults

  • $algo: the algorithm name, such as "sha256", "md5" or "crc32b". Names are listed by hash_algos().
  • $data: the message to hash. PHP hashes the raw bytes of the string, so the same text in a different encoding gives a different digest.
  • $binary: defaults to false, which returns lowercase hexadecimal digits. With true you get the raw binary digest.
  • $options: defaults to an empty array. The manual says only the "seed" option is supported, and only by the MurmurHash variants. Other algorithms do not use it.

Return value and failures

The function returns a string. In hex mode its length is twice the digest size in bytes, so SHA-256 gives 64 characters. In binary mode the string holds the raw bytes, so SHA-256 gives 32 bytes. These bytes are usually not printable.

If $algo is not a known algorithm, hash() throws a ValueError. Before PHP 8.0 it returned false and emitted a warning. The $options parameter was added in PHP 8.1.

Example 1: a basic digest

This is the manual's own example. The output is always 64 hex characters for SHA-256.

Example
<?php
echo hash('sha256', 'The quick brown fox jumped over the lazy dog.'), "\n";
Output
68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483

Example 2: comparing algorithms

Different algorithms give digests of different lengths for the same input. Looping over a list of names keeps the code compact.

Example
<?php
$data = 'abc';
$digests = [];
foreach (['md5', 'sha1', 'sha256'] as $algo) {
    $digests[$algo] = hash($algo, $data);
}
echo json_encode($digests, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"md5":"900150983cd24fb0d6963f7d28e17f72","sha1":"a9993e364706816aba3e25717850c26c9cd0d89d","sha256":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}

Example 3: hex versus binary, and unknown algorithms

The binary form is half the length of the hex form. Passing an unknown algorithm name raises a ValueError that you can catch.

Example
<?php
$hex = hash('sha256', 'abc');
$raw = hash('sha256', 'abc', true);

try {
    hash('not-an-algo', 'abc');
    $error = 'none';
} catch (ValueError $e) {
    $error = get_class($e);
}

echo json_encode([
    'hexLength' => strlen($hex),
    'binaryLength' => strlen($raw),
    'sameDigest' => bin2hex($raw) === $hex,
    'error' => $error,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"hexLength":64,"binaryLength":32,"sameDigest":true,"error":"ValueError"}

Edge cases

  • Hashing an empty string is valid and returns the algorithm's digest of zero bytes.
  • Hex output is always lowercase. Normalise case if you compare against digests from other systems.
  • Binary output may contain null bytes and invalid UTF-8. Do not pass it to json_encode() or print it into a text page. Use bin2hex() or base64_encode() first.
  • Large inputs have to be in memory as one string. For streamed data use hash_init(), hash_update() and hash_final(). For files use hash_file().
  • Algorithm availability depends on the build. Check hash_algos() if the name comes from configuration.

Common mistakes

  1. Using a fast digest for passwords. Digests like SHA-256 are designed to be fast, which helps attackers. Use password_hash() instead.
  2. Using MD5 or SHA-1 where security matters. They are fine for non-adversarial checksums, but should not be chosen for signatures or integrity against an attacker.
  3. Comparing secrets with === or ==. Use hash_equals() to compare a known digest with user-supplied input in constant time.
  4. Expecting a hash to be reversible. A digest is one-way. You can only hash a guess and compare.
  5. Ignoring the $binary flag. Mixing hex and raw digests in comparisons silently fails because the strings differ in length and content.
  6. Passing $options to every algorithm. The seed is only for MurmurHash variants.
  • hash_algos() lists the supported algorithm names.
  • hash_init() starts an incremental hashing context.
  • hash_file() hashes the contents of a file.
  • hash_hmac() creates a keyed digest using HMAC.
  • hash_equals() compares digests safely.

Recap

hash() takes an algorithm name and a string, and returns a lowercase hex digest by default or raw bytes when $binary is true. An unknown algorithm throws ValueError. It is the right tool for fingerprints and checksums, but not for password storage, and secrets should be compared with hash_equals().

Tested examples

A basic SHA-256 digest
<?php
echo hash('sha256', 'The quick brown fox jumped over the lazy dog.'), "\n";
Output
68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483
Comparing md5, sha1 and sha256
<?php
$data = 'abc';
$digests = [];
foreach (['md5', 'sha1', 'sha256'] as $algo) {
    $digests[$algo] = hash($algo, $data);
}
echo json_encode($digests, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"md5":"900150983cd24fb0d6963f7d28e17f72","sha1":"a9993e364706816aba3e25717850c26c9cd0d89d","sha256":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}
Hex versus binary output and a ValueError
<?php
$hex = hash('sha256', 'abc');
$raw = hash('sha256', 'abc', true);

try {
    hash('not-an-algo', 'abc');
    $error = 'none';
} catch (ValueError $e) {
    $error = get_class($e);
}

echo json_encode([
    'hexLength' => strlen($hex),
    'binaryLength' => strlen($raw),
    'sameDigest' => bin2hex($raw) === $hex,
    'error' => $error,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"hexLength":64,"binaryLength":32,"sameDigest":true,"error":"ValueError"}