Hashing7 min readExamples run on PHP 8.5.10

hash_update()

Learn how hash_update() feeds data into an incremental hashing context in PHP 8.4, with chunked digest examples, edge cases and common mistakes.

Signature

hash_update(HashContext $context, string $data): true
Available since PHP 5.1.27.48.08.18.28.38.48.5All versions →

Purpose

hash_update() adds a piece of data to a hashing context that was created with hash_init(). It is the middle step of PHP's incremental hashing workflow:

  1. hash_init() creates a HashContext for an algorithm.
  2. hash_update() feeds the context one chunk of the message, as many times as needed.
  3. hash_final() finishes the calculation and returns the digest.

This is useful when the message arrives in pieces, or when you do not want to build one large string in memory. The digest is the same as the one-shot hash() would give for the concatenated message.

Signature

Parameters and defaults

  • $context: a HashContext object returned by hash_init(). It must not have been finalized yet. The function modifies this object in place, so you do not need to reassign it.
  • $data: the next chunk of the message. It is treated as raw bytes. Neither parameter has a default value.

Return value and failures

The function always returns true. Since PHP 8.4 the declared return type is true instead of bool. There is no false result to check for, so wrapping the call in an if adds nothing. Failures show up as errors instead. Passing a context that has already been finalized with hash_final() raises an error, and passing something that is not a HashContext raises a TypeError. Before PHP 7.2 the context was a resource rather than an object.

Example 1: Feeding a message in two chunks

The two calls below hash the same bytes as hash('md5', 'hello world').

Example
<?php
$ctx = hash_init('md5');
hash_update($ctx, 'hello');
hash_update($ctx, ' world');
echo hash_final($ctx), "\n";
Output
5eb63bbbe01eeed093cb22bb8f5acdc3

Notice that the space belongs to the second chunk. Every byte you pass is part of the message.

Example 2: Looping over chunks and comparing with hash()

In real code the chunks usually come from a loop. Here the chunks are an in-memory array, and the result is compared with the one-shot function.

Example
<?php
$ctx = hash_init('sha256');
foreach (['a', 'bc'] as $chunk) {
    hash_update($ctx, $chunk);
}
$incremental = hash_final($ctx);
echo json_encode([
    'same' => $incremental === hash('sha256', 'abc'),
    'digest' => $incremental,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"same":true,"digest":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}

How the message is split does not matter. 'a' plus 'bc' gives the same digest as 'abc' in one piece, or as three one-byte chunks.

Example 3: The return value and empty data

The return value is always true, and an empty string leaves the state unchanged.

Example
<?php
$ctx = hash_init('md5');
var_dump(hash_update($ctx, ''));
echo hash_final($ctx), "\n";
Output
bool(true)
d41d8cd98f00b204e9800998ecf8427e

The final digest is the well-known MD5 of the empty message, because no bytes were added.

Edge cases

  • Empty chunks are harmless. They are accepted and do not change the digest.
  • Binary data is fine. The function does not care about encodings, so multibyte text is hashed as its raw bytes. If two systems must agree on a digest, they must agree on the encoding first.
  • Finalized contexts cannot be updated. Once hash_final() has run, start again with a new hash_init(). If you need the digest of a prefix and also of a longer message, call hash_copy() before finalizing so you can continue with the copy.
  • HMAC contexts work the same way. A context created with hash_init() using the HASH_HMAC flag and a key accepts chunks through hash_update() too.
  • Large inputs are the main reason to use this function. Feed pieces of any size, and memory use stays bounded by the chunk size rather than the message size.

Common mistakes

  • Reassigning the result. Writing $ctx = hash_update($ctx, $data); replaces the context with true, and the next call fails. The function does not return the context.
  • Forgetting hash_final(). Updating a context never produces a digest by itself. You must call hash_final() to get the result.
  • Reusing a context after finalizing. A finished context is spent. Create a new one for a new message.
  • Hashing a transformed chunk. If you trim, re-encode or add separators to some chunks but not to the one-shot comparison, the digests will differ. Only the exact bytes passed in matter.
  • Testing the return value. Because the result is always true, an if (!hash_update(...)) branch can never run.
  • Using fast hashes for passwords. Incremental hashing with MD5 or SHA-256 is for checksums and integrity, not for storing passwords. Use password_hash() for that.
  • hash_init() creates the context.
  • hash_update_file() feeds a file into a context.
  • hash_update_stream() feeds an open stream into a context.
  • hash_final() finishes the context and returns the digest.
  • hash_copy() duplicates a context so you can branch the calculation.
  • hash() is the one-shot alternative for messages that are already fully in memory.

Recap

hash_update() appends bytes to a HashContext from hash_init(). It modifies the context in place and always returns true. How you split the message does not change the digest, and you must call hash_final() to read it. Do not update a finalized context, and do not assign the return value back to your context variable.

Tested examples

Feeding a message in two chunks
<?php
$ctx = hash_init('md5');
hash_update($ctx, 'hello');
hash_update($ctx, ' world');
echo hash_final($ctx), "\n";
Output
5eb63bbbe01eeed093cb22bb8f5acdc3
Looping over chunks and comparing with hash()
<?php
$ctx = hash_init('sha256');
foreach (['a', 'bc'] as $chunk) {
    hash_update($ctx, $chunk);
}
$incremental = hash_final($ctx);
echo json_encode([
    'same' => $incremental === hash('sha256', 'abc'),
    'digest' => $incremental,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"same":true,"digest":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}
The return value and empty data
<?php
$ctx = hash_init('md5');
var_dump(hash_update($ctx, ''));
echo hash_final($ctx), "\n";
Output
bool(true)
d41d8cd98f00b204e9800998ecf8427e