Signature
hash_final(HashContext $context, bool $binary = false): stringPurpose
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: aHashContextobject returned byhash_init(). It carries the algorithm and the state of everything fed in so far.$binary: defaults tofalse, which returns the digest as lowercase hexadecimal characters. Withtrue, 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.
$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";{"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.
$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";{"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.
$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";["a9993e364706816aba3e25717850c26c9cd0d89d","TypeError"]
Edge cases
- No updates: you can call
hash_final()right afterhash_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 theHASH_HMACflag and a key produces an HMAC when finalized. The key is given tohash_init(), not tohash_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
- Finalizing twice. Treat the context as consumed after
hash_final(). Store the digest in a variable if you need it again. - Expecting hex from binary mode. Passing
truechanges the format, so comparing it with a hex string fromhash()will fail. - Comparing digests with
==or===for secrets. For MACs or tokens, usehash_equals()to get a constant-time comparison. - Choosing a fast hash for passwords. Incremental hashing is for integrity and fingerprints. For passwords, use
password_hash(). - Mixing algorithms. A digest is only comparable with another digest from the same algorithm and the same input bytes.
Related functions
hash_init()creates the context.hash_update()adds a string to it.hash_update_stream()andhash_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
$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";
{"digest":"5d41402abc4b2a76b9719d911017c592","same":true}
$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";
{"length":32,"hex":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}
$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";
["a9993e364706816aba3e25717850c26c9cd0d89d","TypeError"]