Hashing7 min readExamples run on PHP 8.5.10

hash_init()

Learn how hash_init() creates a HashContext for chunked or HMAC hashing in PHP 8.4, with its parameters, errors, examples and common mistakes.

Signature

hash_init(
    string $algo,
    int $flags = 0,
    string $key = "",
    array $options = []
): HashContext
Available since PHP 5.1.27.48.08.18.28.38.48.5All versions →

Purpose

hash_init() creates a hashing context: an object that remembers the state of a half-finished hash. You feed it data in pieces with hash_update() and finish with hash_final(), which returns the digest. The result matches what hash() would produce for the same data joined together.

Use it when the input arrives in parts, when you do not want to build one large string in memory, or when you need an incremental HMAC. For a short string you already hold in full, plain hash() is simpler.

Signature

The $key parameter is marked #[\SensitiveParameter], so its value is hidden from stack traces.

Parameters and defaults

  • $algo: the algorithm name, such as "sha256" or "md5". hash_algos() lists what your build supports.
  • $flags: defaults to 0, meaning a plain hash. The only supported flag is HASH_HMAC, which switches to keyed HMAC mode.
  • $key: the shared secret. It is required when HASH_HMAC is set and ignored otherwise. The default is an empty string, which is not accepted in HMAC mode.
  • $options: an array of algorithm-specific settings. Currently only the "seed" option is supported, and only by the MurmurHash variants.

Return value and failure cases

The function returns a HashContext object. Since PHP 7.2 this is an object rather than a resource. Pass it to hash_update(), hash_update_stream(), hash_update_file() and hash_final().

Since PHP 8.0, bad input throws a ValueError instead of returning false with a warning. That happens when:

  • the algorithm name is unknown,
  • HASH_HMAC is requested with a non-cryptographic algorithm (for example crc32b, adler32 or fnv1a32),
  • HASH_HMAC is requested with an empty key.

As of PHP 8.4, passing $options values of the wrong type is deprecated and emits E_DEPRECATED. The manual says this will become a ValueError in the future.

Example 1: chunks equal the one-shot hash

Splitting the input does not change the digest. This is the core guarantee of incremental hashing.

Example
<?php
$ctx = hash_init('sha256');
hash_update($ctx, 'The quick brown fox ');
hash_update($ctx, 'jumped over the lazy dog.');
$incremental = hash_final($ctx);

echo $incremental, PHP_EOL;
echo json_encode($incremental === hash('sha256', 'The quick brown fox jumped over the lazy dog.')), PHP_EOL;
Output
68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483
true

Example 2: incremental HMAC

With HASH_HMAC and a key, the context computes a keyed digest. It equals hash_hmac() over the concatenated data. A SHA-256 hex digest is always 64 characters long.

Example
<?php
$key = 'demo-secret';
$ctx = hash_init('sha256', HASH_HMAC, $key);
foreach (['order=', '1001', '&total=25'] as $chunk) {
    hash_update($ctx, $chunk);
}
$mac = hash_final($ctx);
$expected = hash_hmac('sha256', 'order=1001&total=25', $key);
echo json_encode([$mac === $expected, strlen($mac)]), PHP_EOL;
Output
[true,64]

Example 3: handling invalid setup

Because failures are exceptions, wrap uncertain input, such as a user-selected algorithm name, in try/catch.

Example
<?php
$results = [];
try {
    hash_init('not-a-real-algo');
} catch (ValueError $e) {
    $results[] = get_class($e);
}
try {
    hash_init('sha256', HASH_HMAC);
} catch (ValueError $e) {
    $results[] = get_class($e);
}
echo json_encode($results), PHP_EOL;
Output
["ValueError","ValueError"]

Edge cases

  • One context, one digest. hash_final() finalizes the context. In PHP 8, using it again with hash_update() throws an error. To keep a running state and branch from it, copy the context first with hash_copy().
  • No updates at all. Calling hash_final() right after hash_init() gives the digest of the empty string.
  • Chunk boundaries do not matter. Splitting at any byte offset yields the same digest, even in the middle of a multibyte character, because hashing works on bytes.
  • Output format. hash_final() returns lowercase hex by default. Passing true as its second argument returns raw binary.
  • Seeds. Only MurmurHash variants read "seed" from $options. Other algorithms do not use it.

Common mistakes

  1. Passing HASH_HMAC without a key. The empty default key throws ValueError.
  2. Using a checksum algorithm for HMAC. crc32b and similar algorithms are not allowed with HASH_HMAC.
  3. Putting the key in the wrong position. The key is the third argument. The second is $flags, so hash_init('sha256', 'secret') is wrong.
  4. Reusing a finalized context. Create a new context for every digest, or use hash_copy() before finalizing.
  5. Comparing digests with == or === in security checks. Use hash_equals() to compare MACs in constant time.
  6. Hashing passwords this way. A fast hash is not suitable for storing passwords. Use password_hash() instead.
  • hash_update(): add a string to an active context.
  • hash_update_stream() and hash_update_file(): add data from a stream or file.
  • hash_final(): finish the context and return the digest.
  • hash_algos(): list the available algorithm names.
  • hash() and hash_hmac(): one-shot equivalents.

Recap

hash_init() returns a HashContext for building a digest in stages. Add HASH_HMAC and a non-empty key for incremental HMAC. Invalid algorithms, non-cryptographic algorithms in HMAC mode and empty HMAC keys throw ValueError. Finish with hash_final() and treat the context as spent afterwards.

Tested examples

Chunks produce the same digest as one-shot hash()
<?php
$ctx = hash_init('sha256');
hash_update($ctx, 'The quick brown fox ');
hash_update($ctx, 'jumped over the lazy dog.');
$incremental = hash_final($ctx);

echo $incremental, PHP_EOL;
echo json_encode($incremental === hash('sha256', 'The quick brown fox jumped over the lazy dog.')), PHP_EOL;
Output
68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483
true
Incremental HMAC matches hash_hmac()
<?php
$key = 'demo-secret';
$ctx = hash_init('sha256', HASH_HMAC, $key);
foreach (['order=', '1001', '&total=25'] as $chunk) {
    hash_update($ctx, $chunk);
}
$mac = hash_final($ctx);
$expected = hash_hmac('sha256', 'order=1001&total=25', $key);
echo json_encode([$mac === $expected, strlen($mac)]), PHP_EOL;
Output
[true,64]
ValueError for unknown algorithm and missing HMAC key
<?php
$results = [];
try {
    hash_init('not-a-real-algo');
} catch (ValueError $e) {
    $results[] = get_class($e);
}
try {
    hash_init('sha256', HASH_HMAC);
} catch (ValueError $e) {
    $results[] = get_class($e);
}
echo json_encode($results), PHP_EOL;
Output
["ValueError","ValueError"]