---
title: "Bcrypt Generator: Hash and Verify Passwords in Your Browser"
description: "Generate and verify bcrypt hashes with any cost and a $2b$, $2y$ or $2a$ prefix. Runs in your browser, nothing sent, plus how to pick the right cost."
image: "https://cdn.sethserver.com/blog-images/bcrypt-generator-social-1789656941.webp"
---

# Bcrypt Generator: Hash and Verify Passwords in Your Browser

## Hash a password

Password to hash

0 bytes

Cost factor

Prefix

Cost 15 and 16 can take many seconds in a browser tab, longer on a phone. The page stays usable while it works, and your server will be several times faster than JavaScript.

...

Prefix

Cost

Salt (22 chars)

Hash (31 chars)

## Verify a bcrypt hash

Password

bcrypt hash

Paste a hash and a password, then check.

Everything above runs in this tab with [bcryptjs](https://github.com/dcodeIO/bcrypt.js) (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
```

| Part | Value | Meaning |
| --- | --- | --- |
| Prefix | `$2b$` | Which bcrypt revision made it (more below) |
| Cost | `12` | Always two digits; 212 rounds |
| Salt | `aISvyupLrL8w4lyQoik6wO` | 22 characters encoding 16 random bytes, in bcrypt's own base64 alphabet |
| Hash | `bPvzewCSqyrM0Yt5YPqMPl8nRFsO5sm` | 31 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:

- **Login is a CPU-bound request.** At 300 ms per hash, one core handles about three logins a second. Rate limit your login endpoint or a bored attacker can use your own password hashing as a denial of service.
- **Defaults vary.** Python bcrypt's `gensalt()` defaults to 12, PHP 8.4 raised `password_hash`'s bcrypt default from 10 to 12, Go's `bcrypt.DefaultCost` is 10, and Apache's `htpasswd -B` still defaults to 5. Set the cost explicitly so a library upgrade doesn't change it for you.
- **Raise it over time.** Because the cost is stored in each hash, you can bump it whenever hardware gets faster. On the next successful login, check the stored cost and rehash if it's lower (PHP has `password_needs_rehash` for exactly this).
- **This page is not your benchmark.** JavaScript bcrypt is several times slower than the native code in PHP, Python or Go, so the timing shown above only tells you how slow your browser is.

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

| Library | Password longer than 72 bytes |
| --- | --- |
| Python `bcrypt` 4.x | Silently truncated. A 73-byte password verifies against the hash of its first 72 bytes. |
| Python `bcrypt` 5.0 | `hashpw` and `checkpw` both raise `ValueError`. |
| PHP `password_hash` (bcrypt) | Truncated to 72 bytes, as the manual warns. |
| Go `golang.org/x/crypto/bcrypt` | `GenerateFromPassword` returns `ErrPasswordTooLong`. |
| Node `bcrypt` and `bcryptjs` | Only 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:

- **Cap passwords at 72 bytes** at signup and say so. Simple and honest.
- **Pre-hash carefully.** OWASP calls naive pre-hashing dangerous (a raw digest can contain NUL bytes, and it opens the door to password shucking) and, if you must, suggests bcrypt of the base64 of an HMAC-SHA-384 keyed with a pepper stored outside the database. It's a one-way door: every existing hash has to migrate.
- **Use Argon2id**, which has no such limit.

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

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

- **$2a$** is the revised original, which pinned down that strings are UTF-8 and include the terminating NUL. Go's x/crypto still writes `$2a$`.
- **$2x$ and $2y$** came out of a June 2011 bug in crypt\_blowfish, the implementation PHP used, which mishandled characters with the high bit set. `$2x$` marks hashes made by the buggy code and `$2y$` marks correct ones. PHP's `password_hash` and Apache's `htpasswd -B` write `$2y$`.
- **$2b$** came from OpenBSD in February 2014, after it turned out their code stored the password length in an 8-bit value, so passwords over 255 bytes wrapped around. Python bcrypt, the Node `bcrypt` package and bcryptjs write `$2b$`.

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](https://www.sethserver.com/security/api-key-generator.html) or [secret key generator](https://www.sethserver.com/security/secret-key-generator.html) has 128 bits or more of real entropy, and nobody is brute forcing that no matter how fast the hash is. Meanwhile:

- Every authenticated API request now pays for a full bcrypt, which turns a cheap lookup into hundreds of milliseconds of CPU.
- The salt means you can't look a key up by its hash. You end up storing a key ID next to it or, worse, scanning rows.
- Long keys with a shared prefix (like `sk_live_`) can run past 72 bytes, and a truncating library quietly stops checking the tail end.

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](https://www.sethserver.com/security/htpasswd-generator.html).

## Common bcrypt mistakes

- **Storing the hash in a 60-character column forever.** bcrypt hashes are 60 characters, but if you ever move to Argon2id the strings get longer. PHP's manual recommends 255 characters for `password_hash` output. Give yourself room.
- **Comparing hashes with `==`.** You can't hash the login attempt and compare strings, because the new hash has a new salt. Always use the library's verify function.
- **Supplying your own salt.** PHP 8 ignores a `salt` option and prints a warning saying so. Let the library pull random bytes.
- **Normalizing the password differently at signup and login.** Trimming whitespace or changing Unicode normalization in one path but not the other locks people out. Pick a rule and apply it in one place.
- **Logging the request body on the login route.** The best hashing in the world won't help if the plaintext is in your log aggregator.

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](https://www.sethserver.com/security/strong-random-password-generator.html) for other lengths. For the bigger picture, I wrote up [my opinionated security best practices](https://www.sethserver.com/security/seths-opinionated-security-best-practices.html).

## 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](https://www.sethserver.com/security/htpasswd-generator.html), [strong random password generator](https://www.sethserver.com/security/strong-random-password-generator.html), [API key generator](https://www.sethserver.com/security/api-key-generator.html), [secret key generator](https://www.sethserver.com/security/secret-key-generator.html), and [passphrase generator](https://www.sethserver.com/security/strong-random-passphrase-generator.html).

-Sethers


```json
{"@context": "https://schema.org", "@graph": [{"@type": "WebApplication", "name": "Bcrypt Generator", "url": "https://www.sethserver.com/security/bcrypt-generator.html", "description": "Generate and verify bcrypt password hashes in your browser with a selectable cost factor and $2a$, $2b$ or $2y$ prefix. Nothing is sent to a server.", "applicationCategory": "DeveloperApplication", "operatingSystem": "Web", "author": {"@type": "Person", "name": "Seth Black"}, "offers": {"@type": "Offer", "price": "0", "priceCurrency": "USD"}}, {"@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "Is it safe to type a real password into this bcrypt generator?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "What cost factor should I use for bcrypt?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Why does the same password give a different bcrypt hash every time?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "What is the difference between $2a$, $2b$ and $2y$?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Does bcrypt really ignore everything after 72 bytes?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Should I hash API keys or session tokens with bcrypt?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Should I use bcrypt or Argon2id for a new project?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}, {"@type": "Question", "name": "Why does a high cost factor take longer here than on my server?", "acceptedAnswer": {"@type": "Answer", "text": "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."}}]}]}
{"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "sethserver", "item": "https://www.sethserver.com/"}, {"@type": "ListItem", "position": 2, "name": "Tools", "item": "https://www.sethserver.com/tools/"}, {"@type": "ListItem", "position": 3, "name": "Bcrypt Generator: Hash and Verify Passwords in Your Browser", "item": "https://www.sethserver.com/security/bcrypt-generator.html"}]}
```
