Skip to content

PHP Tutorial Authentication

password_hash()

Lesson 283 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

password_hash() is the function for hashing passwords. It creates a salt, runs a slow algorithm and returns one string that contains everything needed to verify the password later.

index.php
PHP
<?php
$hash = password_hash("my secret password", PASSWORD_DEFAULT);

echo strlen($hash) . "\n";
echo substr($hash, 0, 7);
Output
OUTPUT
60
$2y$10$

The hash is 60 characters here, and it starts with $2y$10$:

  • $2y$ means the bcrypt algorithm.
  • 10 is the cost (how much work it takes).
  • The rest is the salt and the hash.

Every call gives a different result

index.php
PHP
<?php
$a = password_hash("secret", PASSWORD_DEFAULT);
$b = password_hash("secret", PASSWORD_DEFAULT);

var_dump($a === $b);
var_dump(password_verify("secret", $a) && password_verify("secret", $b));
Output
OUTPUT
bool(false)
bool(true)

The salt is random, so the strings differ, but each one verifies the same password. You cannot compare hashes with ===. Use password_verify().

The algorithms

ConstantAlgorithmNotes
PASSWORD_DEFAULTThe best one PHP recommendsCurrently bcrypt. PHP may change it in a future version
PASSWORD_BCRYPTbcryptWidely used, available everywhere
PASSWORD_ARGON2I, PASSWORD_ARGON2IDArgon2Modern and memory-hard. Needs PHP built with Argon2 support

Use PASSWORD_DEFAULT unless you have a reason. It is the safe choice, and PHP can upgrade it for you over time.

The cost option

A higher cost means a slower hash, which is safer but uses more of your server’s time.

index.php
PHP
<?php
$hash = password_hash("secret", PASSWORD_BCRYPT, ["cost" => 12]);

echo substr($hash, 0, 7);
Output
OUTPUT
$2y$12$

Choose a cost that takes roughly 50 to 250 milliseconds on your server. A common method is to test increasing values and pick the largest one that stays fast enough for a login.

index.php
PHP
<?php
foreach ([10, 11, 12] as $cost) {
    $start = microtime(true);
    password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
    $time = round((microtime(true) - $start) * 1000);

    echo "cost $cost takes about " . ($time > 0 ? "a measurable time" : "no time") . "\n";
}
Output
OUTPUT
cost 10 takes about a measurable time
cost 11 takes about a measurable time
cost 12 takes about a measurable time

(The real numbers depend on your hardware. Print $time yourself to see them. Each cost step roughly doubles the time.)

Inspect a hash

index.php
PHP
<?php
$info = password_get_info(password_hash("x", PASSWORD_BCRYPT, ["cost" => 11]));

echo $info["algoName"] . ", cost " . $info["options"]["cost"];
Output
OUTPUT
bcrypt, cost 11

The 72-byte limit of bcrypt

bcrypt only uses the first 72 bytes of the password. Anything longer is ignored.

index.php
PHP
<?php
$long = str_repeat("a", 72);
$hash = password_hash($long, PASSWORD_BCRYPT);

var_dump(password_verify($long . "EXTRA", $hash));
Output
OUTPUT
bool(true)

The extra characters made no difference. This is rarely a problem for real passwords, but it is why very long inputs should be handled with care. Some developers pre-hash long passwords. Argon2 has no such short limit.

Where to keep the hash

The database column needs room. Use VARCHAR(255), as bcrypt hashes are 60 characters today, but other algorithms and future defaults are longer.

Do not trim or change the password

Hash exactly what the user typed. A password may legitimately start or end with a space.

dangerous
Never log or print the plain password
Not in error messages, not in debug output, not in the log file. Mark the parameter as sensitive in PHP 8.2 and later with #[\SensitiveParameter], so it is hidden in stack traces.