password_hash
Creates a secure, salted one-way hash of a password using bcrypt or Argon2.
password_hash(#[\SensitiveParameter] string $password, string|int|null $algo, array $options = []): string- Added in PHP 5.5.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$password | string | The user's plain-text password. With PASSWORD_BCRYPT it is truncated to 72 bytes. |
$algo | string|int|null | A password algorithm constant such as PASSWORD_DEFAULT, PASSWORD_BCRYPT or PASSWORD_ARGON2ID. null selects the default algorithm. |
$optionsoptional | array= [] | Algorithm options: cost for bcrypt, or memory_cost, time_cost and threads for Argon2. Omitted keys use the algorithm defaults. |
Return value
stringThe hashed password. The algorithm, cost and a randomly generated salt are all embedded in the returned string, so nothing else needs to be stored.
Errors
Throws ValueError if algo is not a valid password hashing algorithm ID, and Error if hashing fails for an unknown reason. Before PHP 8.0.0 it returned false in these cases.
Changelog
- PHP 8.4.0The default cost for PASSWORD_BCRYPT was raised from 10 to 12, so hashes made on 8.4+ are roughly four times slower to compute than earlier defaults.
- PHP 8.3.0When a ValueError is thrown because salt generation failed, the underlying Random\RandomException is attached as the previous exception.
- PHP 8.0.0No longer returns false on failure: a ValueError is thrown for an invalid algorithm and an Error for unknown failures.
- PHP 8.0.0The algo parameter became nullable, and an explicitly supplied salt option is now ignored entirely.
- PHP 7.4.0The algo parameter expects a string; the PASSWORD_* constants changed from ints to strings, though ints are still accepted.
- PHP 7.3.0PASSWORD_ARGON2ID was added.
- PHP 7.2.0PASSWORD_ARGON2I was added.
password_hash is the function you should be using to store passwords. It generates a cryptographically secure random salt for you, applies a deliberately slow algorithm, and returns one self-contained string. Store that string in a single column; you never store the salt, the cost or the algorithm separately.
Because the salt is random, hashing the same password twice gives two different strings. That is correct and expected. It also means you can never compare hashes with === or look a user up by their hashed password — verification always goes through password_verify.
Choosing an algorithm
Pass PASSWORD_DEFAULT unless you have a reason not to. It currently means bcrypt, but it is designed to change in future PHP releases, which is precisely why your column should be VARCHAR(255) rather than sized to today's 60-character bcrypt output. A too-narrow column silently truncates the hash and every login fails afterwards.
Cost
The cost option controls how slow bcrypt is; each increment doubles the work. PHP 8.4 raised the default from 10 to 12. Tune it so hashing takes roughly 100–250 ms on your production hardware, then use password_needs_rehash to upgrade old hashes as users log in.
Example
When to use it
- Hash a password at signup before inserting the user row
- Hash the replacement password during a password-reset flow
- Re-hash an existing password at login with a stronger cost after raising your security baseline
- Generate a hashed API or admin token that is stored but never needs to be read back
Frequently asked questions
Why does password_hash return a different result every time?
It generates a new random salt on every call, so the same password never produces the same hash twice. That is what stops attackers from spotting shared passwords across accounts, and it is why you verify with password_verify instead of comparing strings.
How long should the database column for a password hash be?
VARCHAR(255). Bcrypt produces 60 characters today and Argon2id around 96, but PASSWORD_DEFAULT is designed to change in future PHP versions, and a column that is too short truncates the hash so every subsequent login fails.
Should I add my own salt or pepper to password_hash?
Do not pass a salt: the salt option has been ignored since PHP 8.0 and PHP generates a secure one itself. A pepper (a secret key kept outside the database) is a valid extra layer, but apply it with hash_hmac before hashing, never as the salt option.
More examples
Cost controls how slow hashing is
Each step of two in cost multiplies the time by about four, because every increment doubles the number of bcrypt rounds. This is the measurement you run on your own production hardware to pick a cost: aim for roughly 100-250 ms, which is unnoticeable at login but brutal for an offline cracker.
<?php
foreach ([4, 8, 10] as $cost) {
$start = microtime(true);
password_hash('hunter2', PASSWORD_BCRYPT, ['cost' => $cost]);
$ms = (microtime(true) - $start) * 1000;
printf("cost %2d: %7.2f ms\n", $cost, $ms);
}Bcrypt truncates at 72 bytes
Both verifications return true: bcrypt ignored everything past the 72nd byte, so a much longer passphrase authenticates against the shorter one's hash. Argon2id has no such limit, which is the main reason to prefer it when users may set long passphrases.
<?php
$base = str_repeat('a', 72);
$hash = password_hash($base, PASSWORD_BCRYPT, ['cost' => 4]);
var_dump(password_verify($base, $hash));
var_dump(password_verify($base . 'TOTALLY-DIFFERENT-SUFFIX', $hash));
$info = password_get_info($hash);
echo $info['algoName'], ' cost ', $info['options']['cost'], "\n";
Discussion