bcrypt
bcrypt
Blowfish's key setup, run a few thousand times on purpose.
- Digest
- 184-bit · 23 bytes
- HMAC
- no key mode
- Security
- A password past 72 bytes is refused
- Family
- 1 in bcrypt
Sample
- hex
- ed8529cb1e4d34bdc37a9952219a05d5f1e78496ab37a4
- base64
- 7YUpyx5NNL3DeplSIZoF1fHnhJarN6Q=
Options
Access
- Create
create("bcrypt") - CLI
hashes bcrypt 'hello world' --salt 73616c7473616c7473616c7473616c74 --cost 4 - Tryplayground with the sample above
bcrypt is old. Provos and Mazières built it for OpenBSD in 1999, and it still sits in plenty of password tables. The trick is Blowfish's key setup. It's slow, so bcrypt runs it 2^cost times and calls that a feature. Fair enough.
const result = create("bcrypt").hash("password", {
salt: "73616c7473616c7473616c7473616c74",
cost: 4,
});
result.digest;
// "9f72647acd4f9f84df28e21dca2617f0cd69df0b4d8fcb"
result.options.crypt;
// "$2b$04$a0DqbFLfZFPxWUvya0Dqb.l1HicqzNl2RdIMGbwgWV6Kzn1urLh6q"
The salt is saltsaltsaltsalt in hex. bcrypt takes exactly 16 bytes, no more and no less. Leave it out and 16 random bytes come back in result.options. The digest is the 23 bytes the string carries after its salt. Want the thing a database or .htpasswd stores? That's crypt.
Each step of cost doubles the work. The default is 12, OWASP asks for at least 10. The page above runs cost 4, the lowest there is, so the tab doesn't sit and think. Tool calls stop at 16.
Here's where this package parts ways with OpenBSD. A password past 72 bytes is an error. OpenBSD, crypt_blowfish and htpasswd just drop the rest. So any password with the same first 72 bytes passes, and nobody tells you. Python's bcrypt 5.0 refuses too, which feels like the right call.
$2a$, $2b$ and $2y$ hash the same under that limit. A hash htpasswd wrote as $2y$ checks out with the same salt and cost. $2x$ is the odd one: crypt_blowfish's old sign bug, and this package doesn't run it.
Checking a hash from a dump or a config file in your own code? @agntn/hashes/bcrypt has the bytes and the string as plain functions:
import { bcrypt, bcryptString } from "@agntn/hashes/bcrypt";
const text = new TextEncoder();
const salt = text.encode("saltsaltsaltsalt");
const digest = bcrypt(text.encode("password"), salt, 4);
bcryptString(salt, 4, digest);
// the same $2b$ string as above
No default cost and no random salt here. You pass all three. A salt of the wrong size or a cost outside 4 to 31 throws before Blowfish starts.