Signature
hash_copy(HashContext $context): HashContextPurpose
hash_copy() duplicates an incremental hashing context. You create a context with hash_init(), feed it data with hash_update(), and finish with hash_final(). Finishing a context consumes it. If you want to produce a digest for the data so far and also keep hashing, or if several inputs share the same prefix, copy the context first.
The copy starts with exactly the internal state of the original. After that, the two contexts are independent. Updating one does not change the other.
Signature
The signature is the same in PHP 8.4. Since PHP 7.2, the function accepts and returns a HashContext object rather than a resource.
Parameters and defaults
$context: aHashContextreturned byhash_init(). There are no other parameters and no default values.
The context can be one created for a plain hash algorithm. An incremental context created with options such as HMAC mode is also a HashContext, and the same function applies to it.
Return value and failure cases
The function returns a new HashContext that is a copy of the one you passed in. It does not return a string digest, so you still call hash_final() on the copy when you want the result.
Passing something that is not a HashContext raises a TypeError. Passing a context that has already been finalized also raises a TypeError, because a finalized context no longer holds usable state. Example 3 shows this.
Example 1: continue after taking a digest
The first example shows the classic use. The original context is finalized for the digest of the first part, while the copy continues with more data.
$context = hash_init("sha256");
hash_update($context, "The quick brown fox ");
/* copy context to be able to continue using it */
$copy_context = hash_copy($context);
echo hash_final($context), "\n";
hash_update($copy_context, "jumped over the lazy dog.");
echo hash_final($copy_context), "\n";b29d66e56ed90cce9b0165c43fedec612b60a071974d8be4513e18580d55b5bd 68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483
The first line is the SHA-256 digest of the prefix. The second line is the digest of the full sentence.
Example 2: branching from a shared prefix
Sometimes many values share a long common beginning, such as a namespace or header. Hash the prefix once, copy the context for each value, and finish each copy separately. The example compares each result with the one-shot hash() function instead of printing long digests.
$base = hash_init('sha256');
hash_update($base, 'user:');
$alice = hash_copy($base);
$bob = hash_copy($base);
hash_update($alice, 'alice');
hash_update($bob, 'bob');
$aliceHash = hash_final($alice);
$bobHash = hash_final($bob);
echo json_encode([
'alice' => $aliceHash === hash('sha256', 'user:alice'),
'bob' => $bobHash === hash('sha256', 'user:bob'),
'different' => $aliceHash !== $bobHash,
'base_still_open' => strlen(hash_final($base)) === 64,
]), "\n";{"alice":true,"bob":true,"different":true,"base_still_open":true}
The original $base context was never changed by the branches, so it can still be finalized afterwards.
Example 3: copying a finalized context fails
Once hash_final() has run, the context is spent. Copy before finalizing, not after.
$ctx = hash_init('sha256');
hash_update($ctx, 'abc');
hash_final($ctx);
try {
hash_copy($ctx);
echo "copied\n";
} catch (TypeError $e) {
echo get_class($e), "\n";
}TypeError
Edge cases
- A freshly created context with no data can be copied. Both copies then hash only what you add later.
- Copying is cheap compared with rehashing a large prefix, but it still duplicates internal state, so avoid creating thousands of copies you never use.
- Copying does not finalize anything. The original and the copy each need their own
hash_final()call if you want a digest from both. - Two contexts that receive identical updates after copying produce identical digests. That is expected, since they started from identical state.
Common mistakes
- Copying after
hash_final(). The original is already consumed, so the call throws aTypeError. Take the copy while the context is still open. - Assuming assignment copies state. Writing
$b = $a;gives you another variable that refers to the same context object, so an update through one is visible through the other. Usehash_copy()when you need independent state. - Expecting a string back. The return value is a
HashContext. Callhash_final()to get the hexadecimal digest. - Reusing one context for unrelated messages. If the messages do not share a prefix, create a new context with
hash_init()for each of them. - Comparing digests with
==or===for secrets. For secret values such as MACs, usehash_equals()to compare them.
Related functions
hash_init()creates the incremental context.hash_update()adds more data to a context.hash_final()finishes a context and returns the digest.hash()computes a digest in one call.hash_equals()compares digests without timing leaks.
Recap
Use hash_copy() when one incremental hash must split into two paths. Copy while the context is open, finish each context on its own, and remember that a finalized context cannot be copied.
Tested examples
$context = hash_init("sha256");
hash_update($context, "The quick brown fox ");
/* copy context to be able to continue using it */
$copy_context = hash_copy($context);
echo hash_final($context), "\n";
hash_update($copy_context, "jumped over the lazy dog.");
echo hash_final($copy_context), "\n";b29d66e56ed90cce9b0165c43fedec612b60a071974d8be4513e18580d55b5bd 68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483
$base = hash_init('sha256');
hash_update($base, 'user:');
$alice = hash_copy($base);
$bob = hash_copy($base);
hash_update($alice, 'alice');
hash_update($bob, 'bob');
$aliceHash = hash_final($alice);
$bobHash = hash_final($bob);
echo json_encode([
'alice' => $aliceHash === hash('sha256', 'user:alice'),
'bob' => $bobHash === hash('sha256', 'user:bob'),
'different' => $aliceHash !== $bobHash,
'base_still_open' => strlen(hash_final($base)) === 64,
]), "\n";{"alice":true,"bob":true,"different":true,"base_still_open":true}
$ctx = hash_init('sha256');
hash_update($ctx, 'abc');
hash_final($ctx);
try {
hash_copy($ctx);
echo "copied\n";
} catch (TypeError $e) {
echo get_class($e), "\n";
}TypeError