Cost and Rehashing

Choosing a Cost, Argon2 and Rehashing on Login

The cost is the security dial. For bcrypt it is the cost option, a base-2 work factor where each step doubles the time; PHP 8.4 raised the default from 10 to 12 (Subsection 4.17.9). Pick the highest cost your login endpoint can afford, aiming for 100-250 ms per hash, and re-measure yearly. The stronger modern choice is Argon2id, winner of the Password Hashing Competition, which resists GPU and custom-hardware attacks by being memory-hard. Select it with PASSWORD_ARGON2ID and tune memory_cost (in kibibytes), time_cost and threads.

The library defaults are memory_cost 65536 (64 MiB), time_cost 4 and threads 1. Because the default algorithm and cost move over time, upgrade stored hashes transparently at the one moment you hold the plaintext, a successful login: password_needs_rehash() reports whether a stored hash still meets your current policy.

Argon2id tuned above the defaults, then an old bcrypt hash rehashed on loginPHP
$argon = password_hash('correct horse battery staple', PASSWORD_ARGON2ID,
  ['memory_cost' => 128 * 1024, 'time_cost' => 4, 'threads' => 1]);   // 128 MiB
echo $argon, "\n";
$stored = password_hash('pw', PASSWORD_BCRYPT, ['cost' => 10]);       // an old cost-10 hash
if (password_verify('pw', $stored)
    && password_needs_rehash($stored, PASSWORD_DEFAULT)) {
    $stored = password_hash('pw', PASSWORD_DEFAULT);     // write $stored back to the DB
    echo "rehashed to cost ", password_get_info($stored)['options']['cost'], "\n";
}
Output
$argon2id$v=19$m=131072,t=4,p=1$Njg2ZE13ZHBLdTV3Y2U1Wg$TSfeyX/I7CRnauoEm5qPMu3YcfgyeW8fwdFRdgfF
  Asg
rehashed to cost 12

The Argon2id hash records its parameters (m=131072,t=4,p=1). The cost-10 bcrypt hash still verified, and because it fell short of the current default it was re-hashed to cost 12 and saved; over a few weeks of logins the whole table migrates with no password reset.