sethserver.com
A glowing padlock made of interlocking gears turning slowly on a dark workbench, with a small hourglass beside it suggesting deliberate slowness.

Bcrypt Generator: Hash and Verify Passwords in Your Browser

By Seth Black • Updated: September 17, 2026

Hash a password

0 bytes

...

Verify a bcrypt hash

Paste a hash and a password, then check.

Everything above runs in this tab with bcryptjs (BSD-3-Clause). Salts and generated passwords come from crypto.getRandomValues. Nothing you type is sent anywhere, but for production credentials I still prefer to hash on the server that will check them.

This bcrypt generator hashes a password with the cost factor and prefix you choose, and the second panel checks a password against a hash you already have. I built it because every time I need a one-off bcrypt hash (a seed user, an admin account in a fixture, a Caddy config) I end up writing the same three lines of Python. Now I don't have to, and neither do you.

The rest of the page is the stuff I wish someone had told me before I shipped my first bcrypt login: how to pick the cost, the 72-byte trap, what the prefixes mean, and the one place people use bcrypt that they really shouldn't.

What bcrypt is and what it is for

bcrypt is a password hashing function from 1999, built on the Blowfish cipher by Niels Provos and David Mazières. Its job is narrow: store a human password so that if your database leaks, turning the hashes back into passwords is slow and expensive.

It does that two ways. Every hash gets a random 16-byte salt, so the same password never produces the same hash twice and precomputed tables are useless. And it has a tunable cost: the expensive key setup runs 2cost times, so cost 12 means 4,096 rounds and cost 13 means 8,192. Each step up doubles the work for you and for anyone guessing.

A bcrypt hash is always 60 characters and carries everything needed to check it later:

$2b$12$aISvyupLrL8w4lyQoik6wObPvzewCSqyrM0Yt5YPqMPl8nRFsO5sm
PartValueMeaning
Prefix$2b$Which bcrypt revision made it (more below)
Cost12Always two digits; 212 rounds
SaltaISvyupLrL8w4lyQoik6wO22 characters encoding 16 random bytes, in bcrypt's own base64 alphabet
HashbPvzewCSqyrM0Yt5YPqMPl8nRFsO5sm31 characters encoding 23 bytes of output

Because the salt and cost live in the string, you store one column and never manage salts yourself. Verification reads the salt and cost back out, hashes the candidate password the same way, and compares.

How to pick a bcrypt cost factor

The OWASP Password Storage Cheat Sheet says to use a work factor of 10 or more. That is a floor, not a target. The right number is the highest cost your login server can afford, and the only way to know that is to measure on the hardware that will actually do the hashing.

Pick a time budget for one hash at login. I aim for roughly a quarter to half a second on production hardware; the PHP manual's own cost-finding example targets 350 milliseconds. Then time a few costs:

import time
import bcrypt

for cost in range(10, 16):
    start = time.perf_counter()
    bcrypt.hashpw(b"correct horse battery staple", bcrypt.gensalt(rounds=cost))
    print(cost, round((time.perf_counter() - start) * 1000), "ms")

On my desktop with Python bcrypt 5.0, a couple of runs printed 55 to 65 ms for cost 10, around 210 ms for 12, 420 to 500 ms for 13, and close to a second for 14. A small cloud VM will be slower, and a busy one slower still. Run it where your app runs.

Things to keep in mind when you choose:

The 72-byte limit, and what each library does about it

bcrypt only uses the first 72 bytes of the password. Anything after that has no effect on the hash. Bytes, not characters: plain ASCII is one byte per character, most accented Latin letters are two, many CJK characters three, and an emoji is usually four. The byte counter under the password box above uses UTF-8, which is what every library below hashes.

For a 20-character random password this never matters. It matters when you let users paste 100-character passphrases, when you glue a pepper or username onto the password before hashing, or when you (please don't) bcrypt a long token. What happens next depends on your library:

LibraryPassword longer than 72 bytes
Python bcrypt 4.xSilently truncated. A 73-byte password verifies against the hash of its first 72 bytes.
Python bcrypt 5.0hashpw and checkpw both raise ValueError.
PHP password_hash (bcrypt)Truncated to 72 bytes, as the manual warns.
Go golang.org/x/crypto/bcryptGenerateFromPassword returns ErrPasswordTooLong.
Node bcrypt and bcryptjsOnly the first 72 bytes are used. bcryptjs adds a truncates() helper so you can check first.

I tested the two Python versions side by side, and the change bites during upgrades. If you have old hashes created from long passwords under bcrypt 4.x, checkpw in 5.0 will raise instead of logging those users in. Slicing the input to 72 bytes reproduces the old behavior exactly:

password_bytes = password.encode("utf-8")
ok = bcrypt.checkpw(password_bytes[:72], stored_hash)

For new code, pick one of these on purpose:

$2a$, $2b$ and $2y$: what the prefix means

The prefix records which generation of bug fixes the hash was made with. The history, briefly:

For ordinary passwords a correct implementation computes the same 31-character hash under all three prefixes; only the label differs. I checked that: hashes from this page's JavaScript with $2b$, $2y$ and $2a$ all verified in Python bcrypt 5.0 and PHP 8.4's password_verify, and PHP and Python hashes verified in bcryptjs. So pick the prefix your target expects (it makes diffs and grep less confusing), but don't panic if a hash from one ecosystem shows up in another. The one to be suspicious of is $2x$.

bcrypt vs Argon2id vs scrypt

The current OWASP Password Storage Cheat Sheet ranks them like this:

  1. Argon2id with at least 19 MiB of memory, 2 iterations and a parallelism of 1.
  2. scrypt if Argon2id isn't available, with a CPU/memory cost of at least 217, block size 8 and parallelism 1.
  3. bcrypt for legacy systems, with a work factor of 10 or more and a 72-byte password limit.
  4. PBKDF2 with HMAC-SHA-256 and 600,000 or more iterations when you need FIPS-140 compliance.

The reason Argon2id and scrypt come first is memory. bcrypt needs only a few kilobytes, which makes it cheap to run many guesses in parallel on GPUs and custom hardware. Memory-hard functions force every guess to hold a chunk of RAM, which is expensive to do millions of times at once.

My take: if your framework already uses bcrypt with a decent cost, you don't have an emergency. Keep it, handle the 72-byte issue, and raise the cost as hardware improves. If you're starting fresh and your language has a well-maintained Argon2id binding (PHP has PASSWORD_ARGON2ID when built with Argon2 support), use that.

Don't use bcrypt for API keys or random tokens

I see this one in code reviews a lot. Someone learns that passwords go through bcrypt, then stores API keys, session tokens or password reset tokens the same way. It feels safer. It isn't, and it hurts.

bcrypt's slowness exists to protect secrets humans chose, which are guessable. A key from my API key generator or secret key generator has 128 bits or more of real entropy, and nobody is brute forcing that no matter how fast the hash is. Meanwhile:

Store SHA-256(token) in an indexed column, or HMAC-SHA-256 with a server-side key if you want a stolen database alone to be useless, look it up directly, and compare in constant time. Save bcrypt for the passwords people type.

bcrypt in Python, Node, PHP and Go

Every snippet below was run against current library versions, and each one verified hashes made by this page.

Python (pip install bcrypt):

import bcrypt

password = "correct horse battery staple".encode("utf-8")
if len(password) > 72:
    raise ValueError("password is longer than 72 bytes")

hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
print(hashed.decode())                    # $2b$12$...
print(bcrypt.checkpw(password, hashed))   # True

Node.js with bcryptjs (npm install bcryptjs), the same library this page uses. The native bcrypt package has the same hash and compare calls.

import bcrypt from "bcryptjs";

const password = "correct horse battery staple";
if (bcrypt.truncates(password)) throw new Error("password is longer than 72 bytes");

const hash = await bcrypt.hash(password, 12);
console.log(hash);                                  // $2b$12$...
console.log(await bcrypt.compare(password, hash));  // true
console.log(bcrypt.getRounds(hash));                // 12

PHP, no extension needed:

<?php
$password = 'correct horse battery staple';
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
echo $hash, "\n";   // $2y$12$...

if (password_verify($password, $hash)) {
    if (password_needs_rehash($hash, PASSWORD_BCRYPT, ['cost' => 13])) {
        $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
        // save the new hash
    }
}

Go (go get golang.org/x/crypto/bcrypt):

package main

import (
	"fmt"

	"golang.org/x/crypto/bcrypt"
)

func main() {
	password := []byte("correct horse battery staple")

	hash, err := bcrypt.GenerateFromPassword(password, 12) // ErrPasswordTooLong over 72 bytes
	if err != nil {
		panic(err)
	}
	fmt.Println(string(hash)) // $2a$12$...

	if err := bcrypt.CompareHashAndPassword(hash, password); err == nil {
		cost, _ := bcrypt.Cost(hash)
		fmt.Println("match, cost", cost)
	}
}

Command line, if you have Apache's tools installed. This prints user:hash; the part after the colon is a normal $2y$ bcrypt hash. Leave off the password argument and it prompts instead, which keeps the password out of your shell history.

htpasswd -nB -C 12 user

That last one is also how you make Basic auth files, which has its own page: the htpasswd generator.

Common bcrypt mistakes

If you need a strong password to hash in the first place, the generate password button above makes a 24-character one, or use the strong random password generator for other lengths. For the bigger picture, I wrote up my opinionated security best practices.

bcrypt FAQ

Is it safe to type a real password into this bcrypt generator?

The hashing runs in JavaScript in your browser tab using the bcryptjs library served from this site. The page makes no request containing your password or hash, and you can confirm that in your browser's network tab. That said, the safest real password is one no web page has ever seen, so for production credentials I would generate the hash on the server that will use it, with the code examples on this page.

What cost factor should I use for bcrypt?

Use at least 10, which is the OWASP minimum, and in practice pick the highest cost that keeps one hash on your production hardware inside a budget you can live with at login, often somewhere between a quarter and half a second. For most current servers that lands at 12 or 13. Measure it on the real machine, because every step up doubles the time.

Why does the same password give a different bcrypt hash every time?

Every hash gets a fresh random 16-byte salt, stored as the 22 characters after the cost. Verification reads the salt back out of the stored hash and repeats the calculation, so two different hashes of the same password both verify. That is the point: identical passwords do not produce identical hashes in your database.

What is the difference between $2a$, $2b$ and $2y$?

They mark which implementation fixes a hash was made with. $2y$ came from the 2011 crypt_blowfish fix for characters with the high bit set and is what PHP and Apache htpasswd write. $2b$ came from the 2014 OpenBSD fix for passwords longer than 255 bytes and is what Python bcrypt and bcryptjs write. $2a$ is the older revision Go still writes. For normal passwords a correct implementation produces the same digest for all three, and most libraries verify all three.

Does bcrypt really ignore everything after 72 bytes?

Yes, the algorithm only uses the first 72 bytes of input. Libraries differ in what they do about it: many silently truncate, while Python bcrypt 5.0 and Go's x/crypto/bcrypt refuse longer passwords with an error. Bytes are not characters, so an emoji-heavy or non-Latin password hits the limit sooner.

Should I hash API keys or session tokens with bcrypt?

No. bcrypt is slow on purpose to protect guessable human passwords. A random token with 128 or more bits of entropy cannot be guessed, so the slowness only burns CPU on every request, and the 72-byte limit can silently ignore part of a long key. Store a SHA-256 or HMAC-SHA-256 of the token instead and compare in constant time.

Should I use bcrypt or Argon2id for a new project?

OWASP's Password Storage Cheat Sheet lists Argon2id first, scrypt second, and bcrypt for legacy systems. If your framework already ships bcrypt and you set a sensible cost and handle the 72-byte limit, you are fine. If you are choosing from scratch and have a maintained Argon2id library, use Argon2id.

Why does a high cost factor take longer here than on my server?

This page runs bcrypt in pure JavaScript, which is several times slower than the native C or Rust code behind PHP, Python bcrypt or Go. Use the in-browser timing as a rough guide only, and measure the real cost on the server that will do the hashing.

Related tools: htpasswd generator, strong random password generator, API key generator, secret key generator, and passphrase generator.

-Sethers

Newsletter

One email, once a week.

Notes on databases, systems, and the occasional strong opinion about Python. No spam, unsubscribe anytime.

More to read

All tools →

My Password Generators Had 53 Bits of Entropy

Sep 21, 2026

API Key Generator with Prefix, Checksum and SHA-256

Sep 17, 2026

Django Secret Key Generator (get_random_secret_key)

Sep 17, 2026