Signature
hash_init(
string $algo,
int $flags = 0,
string $key = "",
array $options = []
): HashContextPurpose
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 to0, meaning a plain hash. The only supported flag isHASH_HMAC, which switches to keyed HMAC mode.$key: the shared secret. It is required whenHASH_HMACis 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_HMACis requested with a non-cryptographic algorithm (for examplecrc32b,adler32orfnv1a32),HASH_HMACis 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.
$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;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.
$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;[true,64]
Example 3: handling invalid setup
Because failures are exceptions, wrap uncertain input, such as a user-selected algorithm name, in try/catch.
$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;["ValueError","ValueError"]
Edge cases
- One context, one digest.
hash_final()finalizes the context. In PHP 8, using it again withhash_update()throws an error. To keep a running state and branch from it, copy the context first withhash_copy(). - No updates at all. Calling
hash_final()right afterhash_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. Passingtrueas its second argument returns raw binary. - Seeds. Only MurmurHash variants read
"seed"from$options. Other algorithms do not use it.
Common mistakes
- Passing
HASH_HMACwithout a key. The empty default key throwsValueError. - Using a checksum algorithm for HMAC.
crc32band similar algorithms are not allowed withHASH_HMAC. - Putting the key in the wrong position. The key is the third argument. The second is
$flags, sohash_init('sha256', 'secret')is wrong. - Reusing a finalized context. Create a new context for every digest, or use
hash_copy()before finalizing. - Comparing digests with
==or===in security checks. Usehash_equals()to compare MACs in constant time. - Hashing passwords this way. A fast hash is not suitable for storing passwords. Use
password_hash()instead.
Related functions
hash_update(): add a string to an active context.hash_update_stream()andhash_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()andhash_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
$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;
68b1282b91de2c054c36629cb8dd447f12f096d3e3c587978dc2248444633483 true
$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;
[true,64]
$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;
["ValueError","ValueError"]