Hashing and HMAC

General Hashing, HMAC and Timing-Safe Comparison

Passwords aside, you still need fast hashes: a file checksum, a lookup key, a webhook signature. Use hash() with an explicit algorithm; SHA-256 is the general default, and the non-cryptographic xxh128 is faster for cache keys. Avoid md5() and sha1(), broken for security use (and gone as SQL functions in MySQL 9.6 524 +; Subsection 3.7.9).

To prove a message came from a holder of a shared secret, use an HMAC, not a bare hash of secret and message. To compare a secret, an HMAC, a CSRF token, an API key against its expected value, use hash_equals(), constant-time whether the strings differ in the first byte or the last. An ordinary == returns at the first mismatch, letting an attacker recover a secret byte by byte from the timing.

A fast hash, an HMAC, and a timing-safe comparisonShell
echo hash('sha256', 'Please code PHP well.'), "\n";
$key = 'shared-secret';
$mac = hash_hmac('sha256', 'amount=100&to=42', $key);
echo "hmac: $mac\n";
var_dump(hash_equals($mac, hash_hmac('sha256', 'amount=100&to=42', $key)));  // genuine
var_dump(hash_equals($mac, str_repeat('0', 64)));                            // forged
echo bin2hex(random_bytes(32)), "\n";                                        // a 256-bit token
Output
11633bad9a800a0a914ce37d4d2a2f8f9ccf890cdeeab54519825c5525792ee4
hmac: 6468ccf16db140bdfe131f921f2fd6743f8409ebb1d854b56cba2d66b54acceb
bool(true)
bool(false)
4e107eb3bc3d42a12f455085f294e7ab083c1f170bcffc2d064f3691dfaa4a88

The HMAC verified only when both message and secret matched. For every token, a session identifier, a reset code, an API key, use random_bytes() with bin2hex() (Subsection 4.7.6); rand() and uniqid() are predictable. Remember: random_bytes() to make a secret, hash_equals() to check one, hash_hmac() to bind a message to a key.