Hashing7 min readExamples run on PHP 8.5.10

hash_copy()

Learn how hash_copy() duplicates a HashContext in PHP 8.4 so you can branch an incremental hash, with examples, edge cases and common mistakes.

Signature

hash_copy(HashContext $context): HashContext
Available since PHP 5.3.07.48.08.18.28.38.48.5All versions →

Purpose

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: a HashContext returned by hash_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.

Example
<?php
$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";
Output
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.

Example
<?php
$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";
Output
{"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.

Example
<?php
$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";
}
Output
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

  1. Copying after hash_final(). The original is already consumed, so the call throws a TypeError. Take the copy while the context is still open.
  2. 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. Use hash_copy() when you need independent state.
  3. Expecting a string back. The return value is a HashContext. Call hash_final() to get the hexadecimal digest.
  4. 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.
  5. Comparing digests with == or === for secrets. For secret values such as MACs, use hash_equals() to compare them.
  • 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

Continue hashing after taking a digest of the prefix
<?php
$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";
Output
b29d66e56ed90cce9b0165c43fedec612b60a071974d8be4513e18580d55b5bd
68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483
Branch two digests from a shared prefix
<?php
$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";
Output
{"alice":true,"bob":true,"different":true,"base_still_open":true}
Copying a finalized context throws TypeError
<?php
$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";
}
Output
TypeError