Hashing7 min readExamples run on PHP 8.5.10

hash_final()

Learn how hash_final() completes an incremental hash from hash_init() and hash_update(), returns hex or raw digests, and fails when a context is reused.

Signature

hash_final(HashContext $context, bool $binary = false): string
Available since PHP 5.1.27.48.08.18.28.38.48.5All versions →

Purpose

hash_final() completes an incremental hashing operation. You start with hash_init(), feed data in pieces with hash_update() (or the stream and file variants), and call hash_final() once to get the digest. This is useful when the input arrives in chunks, or when you do not want to build one large string in memory before hashing it.

The result is the same as calling hash() on the concatenated input with the same algorithm. The difference is only in how the data is supplied.

Signature

Parameters and defaults

  • $context: a HashContext object returned by hash_init(). It carries the algorithm and the state of everything fed in so far.
  • $binary: defaults to false, which returns the digest as lowercase hexadecimal characters. With true, you get the raw bytes of the digest.

Return value and failure cases

The function returns a string. In hex mode its length is twice the digest size in bytes. In binary mode its length equals the digest size, so SHA-256 gives 32 bytes.

A context can be finalized only once. After hash_final() runs, the context is spent, and passing it to hash_final() or hash_update() again raises a TypeError in PHP 8. Passing something that is not a HashContext also fails with a TypeError.

The manual's changelog records one change: since PHP 7.2.0 the function accepts a HashContext object instead of a resource.

Example 1: chunked input matches one-shot hashing

The two hash_update() calls below add up to the string hello. The final digest equals what hash('md5', 'hello') returns.

Example
<?php
$ctx = hash_init('md5');
hash_update($ctx, 'hel');
hash_update($ctx, 'lo');
$digest = hash_final($ctx);
echo json_encode(['digest' => $digest, 'same' => $digest === hash('md5', 'hello')], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"digest":"5d41402abc4b2a76b9719d911017c592","same":true}

Example 2: raw binary output

With $binary set to true, the string contains raw bytes, which are generally not printable. Convert them with bin2hex() if you need to display or store them as text.

Example
<?php
$ctx = hash_init('sha256');
hash_update($ctx, 'abc');
$raw = hash_final($ctx, true);
echo json_encode(['length' => strlen($raw), 'hex' => bin2hex($raw)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"length":32,"hex":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}

Example 3: a finalized context cannot be reused

The first call returns the SHA-1 digest. A second call on the same context throws, so the code catches the error and records its class name.

Example
<?php
$ctx = hash_init('sha1');
hash_update($ctx, 'abc');
$first = hash_final($ctx);
$second = null;
try {
    hash_final($ctx);
} catch (Throwable $e) {
    $second = get_class($e);
}
echo json_encode([$first, $second], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
["a9993e364706816aba3e25717850c26c9cd0d89d","TypeError"]

Edge cases

  • No updates: you can call hash_final() right after hash_init(). The result is the digest of an empty message for that algorithm.
  • Chunk boundaries do not matter: splitting the input at different places gives the same digest, as long as the bytes and their order are unchanged.
  • HMAC contexts: a context created with hash_init() using the HASH_HMAC flag and a key produces an HMAC when finalized. The key is given to hash_init(), not to hash_final().
  • Snapshots: if you need the digest of a prefix and also want to keep hashing, call hash_copy() before finalizing and finalize the copy.
  • Binary strings: raw output can contain null bytes and invalid UTF-8, so it is unsafe to pass straight to json_encode() or to print in a page.

Common mistakes

  1. Finalizing twice. Treat the context as consumed after hash_final(). Store the digest in a variable if you need it again.
  2. Expecting hex from binary mode. Passing true changes the format, so comparing it with a hex string from hash() will fail.
  3. Comparing digests with == or === for secrets. For MACs or tokens, use hash_equals() to get a constant-time comparison.
  4. Choosing a fast hash for passwords. Incremental hashing is for integrity and fingerprints. For passwords, use password_hash().
  5. Mixing algorithms. A digest is only comparable with another digest from the same algorithm and the same input bytes.
  • hash_init() creates the context.
  • hash_update() adds a string to it.
  • hash_update_stream() and hash_update_file() add data from a stream or a file.
  • hash_copy() duplicates a context so you can branch.
  • hash() computes a digest in a single call.

Recap

hash_final() ends an incremental hash and returns the digest as lowercase hex by default, or as raw bytes when $binary is true. Call it exactly once per context, keep the result, and use hash_copy() if you need to continue from an intermediate state.

Tested examples

Chunked input matches one-shot hashing
<?php
$ctx = hash_init('md5');
hash_update($ctx, 'hel');
hash_update($ctx, 'lo');
$digest = hash_final($ctx);
echo json_encode(['digest' => $digest, 'same' => $digest === hash('md5', 'hello')], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"digest":"5d41402abc4b2a76b9719d911017c592","same":true}
Raw binary output
<?php
$ctx = hash_init('sha256');
hash_update($ctx, 'abc');
$raw = hash_final($ctx, true);
echo json_encode(['length' => strlen($raw), 'hex' => bin2hex($raw)], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
{"length":32,"hex":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}
A finalized context cannot be reused
<?php
$ctx = hash_init('sha1');
hash_update($ctx, 'abc');
$first = hash_final($ctx);
$second = null;
try {
    hash_final($ctx);
} catch (Throwable $e) {
    $second = get_class($e);
}
echo json_encode([$first, $second], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo "\n";
Output
["a9993e364706816aba3e25717850c26c9cd0d89d","TypeError"]