Signature
hash_pbkdf2(
string $algo,
#[\SensitiveParameter] string $password,
string $salt,
int $iterations,
int $length = 0,
bool $binary = false,
array $options = []
): stringPurpose
hash_pbkdf2() implements PBKDF2, a password-based key derivation function. It takes a password and a salt, then repeatedly applies an HMAC built on a hash algorithm of your choice. The result is a derived key. The iteration count makes each guess slower for an attacker, and the salt makes precomputed tables much less useful.
Typical uses are deriving an encryption key from a passphrase, or interoperating with a system that specifies PBKDF2. The manual notes that PBKDF2 can be used for password storage, but password_hash() or crypt() with Blowfish are better suited to that job.
Signature
Parameters and defaults
$algo: the name of the hash algorithm, such assha256. Valid names are listed byhash_hmac_algos(). Non-cryptographic algorithms are not allowed.$password: the secret input. TheSensitiveParameterattribute keeps its value out of stack traces.$salt: the salt. It should be generated randomly for real use, and it must be stored so that you can derive the same key again.$iterations: how many internal iterations to run. It must be greater than 0. Larger values cost more time.$length: the output length. The default0means the full output of the algorithm. The unit depends on$binary, as described below.$binary:false(the default) returns lowercase hexadecimal.truereturns raw bytes.$options: an options array. Only theseedkey is supported, and only by the MurmurHash variants, so it is irrelevant for PBKDF2 with cryptographic algorithms. Leave it empty.
Return value and failure cases
The function returns a string: the derived key as lowercase hexits, or raw bytes if $binary is true.
Since PHP 8.0.0 it throws a ValueError on failure. Before that it returned false and emitted a warning. The error is thrown when:
- the algorithm is unknown,
$iterationsis 0 or less,$lengthis below 0,- the salt is too long (greater than
INT_MAX - 4).
PHP 7.2.0 disabled non-cryptographic algorithms such as crc32 and adler32, so they are rejected as well.
Example 1: known test vectors
RFC 6070 publishes PBKDF2-HMAC-SHA1 test vectors, so this output can be checked against a standard. The $length of 40 hex characters equals 20 bytes, which is the full SHA-1 size.
$results = [];
foreach ([1, 2, 4096] as $iterations) {
$results[] = hash_pbkdf2('sha1', 'password', 'salt', $iterations, 40);
}
echo json_encode($results), "\n";["0c60c80f961f0e71f3a9b524af6012062fe037a6","ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957","4b007901b765489abead49d926f721d065a429c1"]
More iterations give a completely different key, even with the same password and salt.
Example 2: hex length versus binary length
The meaning of $length changes with $binary. In hex mode it counts output characters. In binary mode it counts bytes. To get the same key material, halve the length for binary output.
$hex = hash_pbkdf2('sha256', 'password', 'salt', 1, 20);
$raw = hash_pbkdf2('sha256', 'password', 'salt', 1, 10, true);
echo json_encode([
'hex' => $hex,
'binaryHex' => bin2hex($raw),
'bytes' => strlen($raw),
'same' => $hex === bin2hex($raw),
]), "\n";{"hex":"120fb6cffcf8b32c43e7","binaryHex":"120fb6cffcf8b32c43e7","bytes":10,"same":true}
Example 3: handling invalid arguments
Because invalid input throws, wrap calls in try/catch when the algorithm or iteration count comes from configuration.
function tryDerive(string $algo, int $iterations): string
{
try {
return hash_pbkdf2($algo, 'pw', 'salt', $iterations, 16);
} catch (ValueError $e) {
return 'ValueError';
}
}
echo json_encode([
tryDerive('sha256', 0),
tryDerive('crc32', 1000),
tryDerive('nope', 1),
]), "\n";["ValueError","ValueError","ValueError"]
Zero iterations, a non-cryptographic algorithm and an unknown algorithm all fail the same way.
Edge cases
- A
$lengthof 0 returns the algorithm's full output: 64 hex characters for SHA-256, 40 for SHA-1. - A
$lengthlarger than the hash output is allowed. PBKDF2 produces more blocks to fill the request. - The salt is an arbitrary byte string. It may be binary and may contain null bytes.
- An empty password or salt is accepted, but it is poor practice.
Common mistakes
- Treating length as bytes in hex mode.
$length = 16in hex mode gives only 8 bytes of key material. - Using a fixed or empty salt in production. Examples here use
saltfor repeatable output. Real code should generate a random salt, for instance withrandom_bytes(), and store it. - Too few iterations. A low count makes brute force cheap. Choose a count that is as high as your performance budget allows, and review it over time.
- Comparing hashes with
==or===. Usehash_equals()for timing-safe comparison when verifying derived values. - Choosing PBKDF2 for ordinary password storage. Prefer
password_hash()andpassword_verify(). - Passing a non-cryptographic algorithm. Names like
crc32are rejected.
Related functions
password_hash(): the recommended way to store passwords.hash_hkdf(): key derivation from input that is already high-entropy.sodium_crypto_pwhash(): Argon2-based derivation.hash_hmac_algos(): lists algorithm names usable here.hash_equals(): timing-safe string comparison.
Recap
hash_pbkdf2() stretches a password and salt into a derived key using repeated HMAC. Remember that $length counts hex characters unless $binary is true, that the salt must be random and stored, and that bad arguments throw ValueError. For storing login passwords, use password_hash() instead.
Tested examples
$results = [];
foreach ([1, 2, 4096] as $iterations) {
$results[] = hash_pbkdf2('sha1', 'password', 'salt', $iterations, 40);
}
echo json_encode($results), "\n";
["0c60c80f961f0e71f3a9b524af6012062fe037a6","ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957","4b007901b765489abead49d926f721d065a429c1"]
$hex = hash_pbkdf2('sha256', 'password', 'salt', 1, 20);
$raw = hash_pbkdf2('sha256', 'password', 'salt', 1, 10, true);
echo json_encode([
'hex' => $hex,
'binaryHex' => bin2hex($raw),
'bytes' => strlen($raw),
'same' => $hex === bin2hex($raw),
]), "\n";
{"hex":"120fb6cffcf8b32c43e7","binaryHex":"120fb6cffcf8b32c43e7","bytes":10,"same":true}
function tryDerive(string $algo, int $iterations): string
{
try {
return hash_pbkdf2($algo, 'pw', 'salt', $iterations, 16);
} catch (ValueError $e) {
return 'ValueError';
}
}
echo json_encode([
tryDerive('sha256', 0),
tryDerive('crc32', 1000),
tryDerive('nope', 1),
]), "\n";
["ValueError","ValueError","ValueError"]