Generate twice with the same password and you get two different hashes. That is the random salt doing its job, not a bug.
Paste a hash and the password you think made it.
Hash a password with bcrypt at the cost factor you choose, or check an existing hash against a candidate password. Use it to seed a users table, or to work out why a login that should succeed does not.
Generate twice with the same password and you get two different hashes. That is the random salt doing its job, not a bug.
Paste a hash and the password you think made it.
$2a$12$Kix8Zt4hOd0dLh2S1r3qKuY8n2wYyO7rF1sQ8uJ5mCvR0pE9tXaGe
│ │ │
│ │ └─ 22 chars of salt, then 31 chars of hash
│ └──── cost factor: 2^12 iterations
└─────── algorithm version Always compare with the verify function, never as strings. The salt lives inside the hash, so you never store it separately and two hashes of the same password never match. Verification reads the cost and salt back out of the stored hash, runs the candidate password through the same process, and compares the results.
Laravel's default and PHP's until recently. Roughly 60 to 100 ms on a modern server. Acceptable, and the value most existing databases use.
The usual recommendation today, around a quarter of a second per hash. That is 4 times the attacker's work of cost 10, and still too short for a user to notice at login.
About a second per hash. Reasonable for a handful of admin accounts. On a busy login endpoint the requests queue, and without rate limiting the endpoint becomes a denial-of-service vector.
You cannot rehash what you do not have in plain text. Check the cost at login instead, and rehash the password then, while you briefly have it. PHP's password_needs_rehash() does this check.
The 72-byte limit is real. Bcrypt ignores anything past 72 bytes of input, without an error. For typed passwords that rarely matters. It does matter if you pre-hash with SHA-256 and pass the hex digest in, because 64 hex characters plus a pepper can cross the limit.
$2a$, $2b$ and $2y$ are interchangeable. The differences are historical: $2y$ came from a PHP fix for a 2011 bug, $2b$ from the OpenBSD equivalent. Every current library verifies all three, so a hash made here works with PHP's password_verify().
Argon2id is the newer answer. It is memory-hard (each guess needs a large block of RAM), which cuts the GPU advantage in a way bcrypt cannot. Choose it for a new system if your platform supports it. If you already have bcrypt at cost 10 or higher, there is no urgent reason to migrate.
<?php
// Hash on registration or password change
$hash = password_hash($plain, PASSWORD_BCRYPT, ['cost' => 12]);
// Verify on login
if (password_verify($plain, $hash)) {
// Upgrade the cost transparently while you have the plain text
if (password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 12])) {
$newHash = password_hash($plain, PASSWORD_BCRYPT, ['cost' => 12]);
// save $newHash
}
echo "logged in\n";
}
// PASSWORD_DEFAULT follows PHP's current recommendation, which means
// it can change between versions. Store the column as VARCHAR(255). Do not skip password_needs_rehash. It raises the cost factor across an existing user base at each login, without asking anyone to reset a password.
// npm i bcrypt (native, fast) or bcryptjs (pure JS, portable)
const bcrypt = require("bcrypt");
const hash = await bcrypt.hash(plain, 12);
if (await bcrypt.compare(plain, hash)) {
console.log("logged in");
}
// Always the async form on a server. The sync version blocks the
// event loop for the whole quarter of a second, which at cost 12
// and any real traffic is a self-inflicted outage.
// Reading the cost back out of a stored hash
console.log(bcrypt.getRounds(hash)); // 12 # pip install bcrypt
import bcrypt
password = plain.encode("utf-8")
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
if bcrypt.checkpw(password, hashed):
print("logged in")
# bcrypt only looks at the first 72 bytes. If you might be handed a
# long passphrase, hash it first and base64 the digest, or use Argon2:
# pip install argon2-cffi
# from argon2 import PasswordHasher
# ph = PasswordHasher(); h = ph.hash(plain); ph.verify(h, plain) Bcrypt has been the sensible default since 1999, built on the Blowfish key schedule so it could be made slower as hardware improved. The adjustable cost is the core of the design: an attacker's GPU gets faster every year, so you raise the cost. Argon2id, which won the 2015 Password Hashing Competition, adds memory hardness, making GPU and ASIC attacks much less economical. For a new system, choose Argon2id. If you already have bcrypt at cost 10 or 12, there is no urgent reason to migrate.
None of this helps if the password is reused. A unique password per site and multi-factor authentication both do more for a user's actual safety than moving from cost 10 to cost 12 ever will.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Because SHA-256 is fast, and speed helps the attacker here: a GPU can try billions of SHA-256 guesses a second. Bcrypt is slow on purpose, and the cost factor lets you make it slower as hardware improves.
10 is Laravel's default and reasonable. 12 is a good target on current server hardware, roughly a quarter of a second per hash. Each step up doubles the work, for the attacker and for your login endpoint.
The salt is random and is stored inside the hash string, in the part after the second dollar sign. That is why two identical passwords produce different hashes, and why you compare with the verify function instead of hashing again and matching strings.
Yes. It is a standard $2a$ / $2b$ hash, which PHP's password_verify(), Symfony's password hasher and bcryptjs in Node all accept.
Nothing leaves your browser, so it is safe in that sense. Still, use a throwaway value: the tool is for testing a login flow or seeding a fixture, not for a password you use.
Bcrypt ignores everything past the first 72 bytes of the input. It rarely matters for passwords, but it does if you feed it a long passphrase or a pre-hashed value, so check before relying on the tail of a long string.
To get a test password to hash, use the password generator. For checksums rather than credentials, use the SHA generator or the MD5 generator, and for signed webhooks the HMAC generator.