Bcrypt Generator & Checker

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.

Hash a password

Generate twice with the same password and you get two different hashes. That is the random salt doing its job, not a bug.

Check a password against a hash

Paste a hash and the password you think made it.

1 Reading a bcrypt hash

Every part has a job

1
2
3
4
5
$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.

2 Choosing a cost factor

Cost 10

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.

Cost 12

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.

Cost 14 and up

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.

Raising it later

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.

3 Three details to check before you ship

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.

How to use the Bcrypt Generator & Checker tool

  1. Type the password into the left panel and pick a cost factor. 10 matches Laravel's default; 12 is the usual recommendation today.
  2. Click Generate Hash. The header shows how long it took, which is the number to watch when choosing a cost.
  3. To check an existing hash, paste it and the candidate password into the right panel and click Verify.
  4. Clicking the generated hash copies it into the verify field, which is a quick way to confirm the round trip works.

How to hash and verify a password with bcrypt in PHP, Node.js and Python

PHP

<?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.

Node.js

// 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

Python

# 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)

Common errors and how to fix them

password_verify always returns false
The stored hash was truncated. A bcrypt hash is exactly 60 characters; a VARCHAR(50) column silently cuts it and nothing will ever match again. Widen the column and reset those passwords.
Hashing the same password twice gives different results
Working as intended. The salt is random and lives inside the hash. Never compare two hashes as strings; always use the verify function.
Logins got slow after raising the cost
Each step doubles the work. Cost 14 is about a second per login, and under concurrency the requests queue. Measure on the production hardware, and rate-limit the login endpoint, because every unauthenticated attempt costs you that CPU time.
Two different long passwords both verify
The 72-byte limit. Everything past it is ignored, so two passphrases sharing a long prefix collide. Pre-hash long input, or use Argon2id.
A $2y$ hash from PHP fails in Node
Some older libraries only recognize $2a$ and $2b$. Current bcrypt and bcryptjs handle all three; if yours does not, upgrade rather than rewriting the prefix by hand.

Good to know

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.

Specifications and references

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.

Frequently Asked Questions

Why bcrypt instead of SHA-256 for passwords?

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.

What cost factor should I pick?

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.

Why is the hash different every time?

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.

Does the output work with Laravel, Symfony and Node?

Yes. It is a standard $2a$ / $2b$ hash, which PHP's password_verify(), Symfony's password hasher and bcryptjs in Node all accept.

Is it safe to type a real password here?

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.

What is the 72-byte limit?

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.

Related

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.

More Security & Hashing Tools

Browse all security & hashing tools

Copied to clipboard!