#Bun.password.verify does not work with bCrypt

1 messages ยท Page 1 of 1 (latest)

compact zinc
#
console.log(data.password);
const hash = await Bun.password.hash(data.password, {
  algorithm: 'bcrypt',
  cost: 10
});
console.log(hash);
console.log(await Bun.password.verify(data.password, hash, 'bcrypt'));
#

it works with Argon2id, but not with bcrypt

#

Running Bun 1.0.27 on latest MacOS

regal stone
#

bun is supposed to automatically detect the hash type from the content, so passing in "bcrypt" as the third argument does nothing

#

I'll see if I can reproduce on windows and linux tomorrow

compact zinc
#

that's why I tried passing it and it still doesn't work

regal stone
#
$ bun run bcrypt_hash.ts
Enter Password: some password
Read: some password
Hash: $2b$10$ut/yHDE/4j67EzTM8X7dc.Po3/V05M4zf2latauaLaU8MiGxsWH6y
Verify result: true

$ cat bcrypt_hash.ts
const pass = prompt("Enter Password:");
console.log(`Read: ${pass}`);

const hash = Bun.password.hashSync(pass, { algorithm: 'bcrypt', cost: 10 });
console.log(`Hash: ${hash}`);

console.log(`Verify result: ${Bun.password.verifySync(pass, hash)}`);

A similar program seems to work fine on linux

#

I'll try the async version of the hash functions

#
$ bun run bcrypt_hash_async.ts
Enter Password: another password
Read: another password
Hash: $2b$10$Y8upaJsW6S0NZCAfzlTqq.jFZUYdqrzcVmUCPlzZLGebzdX37WuNO
Verify result: true

Still failed to reproduce on linux

#

Interesting results on windows

$ bun run bcrypt_hash.ts 
Enter Password: some password
Read: some password
Testing Sync
Hash: $2b$10$e7GoVjhSR6NchzXOswVJFuM2KBKYBXHjUhXVRUzucyroHckirEqbu
Verify result: true
Testing Async
Hash: $2b$10$t4X4HomGuokucmkdlE/Ire.vmq2xLqtlFxenz.QarU5CyQHU4K4by
Verify result: true
Hash from sync and async were equal: false
#
const pass = prompt("Enter Password:");
console.log(`Read: ${pass}`);

console.log(`Testing Sync`);

const hash = Bun.password.hashSync(pass, { algorithm: 'bcrypt', cost: 10 });
console.log(`Hash: ${hash}`);

console.log(`Verify result: ${Bun.password.verifySync(pass, hash)}`);

console.log(`Testing Async`);

const hash2 = await Bun.password.hash(pass, { algorithm: 'bcrypt', cost: 10 });
console.log(`Hash: ${hash2}`);

console.log(`Verify result: ${await Bun.password.verify(pass, hash2)}`);

console.log(`Hash from sync and async were equal: ${hash == hash2}`);
#

The hashes from sync and async were different, but both verified correctly

#

it seems that bun's password hashing automatically adds a salt value - this difference in hash when run multiple times also happens on linux

#

that doesn't explain why verify returned false in your case though

night parrot
#

it seems to fail for passwords longer than 72 characters

regal stone
#

yep managed to reproduce that on windows

#

71 is fine, 72 is not

night parrot
#

but that bug should be pretty easy to fix

#

hmm, 72 is fine here, but 73 is not.

#

(Linux)

#

maybe prompt is returning \r but dropping the \n?

regal stone
#

oh yeah it might

#

printing .length does say there's an extra character

#

so yeah 72 is fine, 73 is not

night parrot
#

that matches the code, which does an sha512 if password.length > 72

regal stone
#

interesting

#

so is verify going down a different code path than hash

night parrot
#

yep, I've fixed it locally and will open a PR

regal stone
#

๐Ÿ‘

night parrot
#

test("bcrypt longer than 72 characters is the SHA-512", async () => {
const boop = Buffer.from("hey".repeat(100));
const hashed = await password.hash(boop, "bcrypt");
expect(await password.verify(Bun.SHA512.hash(boop), hashed, "bcrypt")).toBeTrue();
});

#

that doesn't make much sense to me

compact zinc
#

so it seems you guys were able to reproduce the problem?

night parrot
#

were you using passwords longer than 72 characters?

#

er, bytes.

regal stone
#

it looks like it

#

it's the second line in the screenshot

night parrot
#

oh, sorry, missed that

subtle kestrel
#

Bcrypt supposedly has a 72 char length limit so passwords beyond that size are sha256d so they are 64 chars

night parrot
#

sha512 (and used as raw input), but yeah ๐Ÿ™‚

subtle kestrel
#

Oh you figured it out nice

night parrot
#

yeah, just not sure whether it's already too late to break compatibility ๐Ÿ˜•

regal stone
#

I think it makes sense to have verify be able to verify hashed passwords

night parrot
#

I think it's certainly too much to expect of the user to count bytes (not characters), compare to 72, and use a different function in that case, but if people are already doing it...

subtle kestrel
#

It would probably have been reported by then

compact zinc
#

So should I just use Argon2id?

#

But this should probably be fixed at least for verify

night parrot
#

I need to figure out how to print a deprecation warning for "bcrypt" when no strategy is specified for passwords > 72 bytes, that seems best to me.

compact zinc
#

the one that uses bcrypt for Bun.password.hash and Bun.password.verify, with passwords longer than 72 bytes wouldn't work for them either, so I'm not sure if deprecation warning is even needed here

#

Just a fix

#

or will hashes also be different for passwords smaller than 72 bytes and verify wouldn't work anymore for them after the fix?

night parrot
#

can you try now? the simple fix as you describe it was merged

compact zinc
#

Will check thanks

#

Personally I prefer Argon2id over bcrypt as well, but for self-hosters, it would be a lot easier to choose the cost for bcrypt as it only includes one parameter that needs to be changed

#

and if you increase it, it would take longer to hash

#

so you can as well make a simple function that loops thru cost and use the one that needs at least 350ms to hash as an example

#

so the "best" cost would be used automatically depending on the server performance

#

with Argon2id there is just too much parameters

#

and it would confuse everyone

#

also parallelism is not supported in Argon2id?

#

or hash length

#

but on back-end with Bun I can't increase parallelism or change hash length

night parrot
#

doesn't argon2id Just Work with the default configuration?

compact zinc
#

for example if I want to generate hash on FE and BE

night parrot
#

(why is the back end hashing passwords at all?)

compact zinc
#

and verify on both sides

night parrot
#

oh, verify ๐Ÿ™‚

compact zinc
#

yeah

#

this could also reduce server side load and make client suffer with hashing

#

client resources are free anyways

#

also by hashing on FE and sending hashed password on BE, this would prevent server ever knowing user password in plain text

#

as most users use 1 password for everything

night parrot
#

as I said, crypto is subtle and it's easy to get wrong. I'm not sure what I think about the whole idea of expensive password hashes

compact zinc
#

what do you usually use to hash user passwords?

night parrot
#

IMHO it's probably better to use deliberately cheap hashes, then run a large dictionary against all substrings of the password to find out whether it's weak

#

I've not had to make that decision for a live site in ages. I honestly would excuse myself to do more research to see whether my ideas still make sense ๐Ÿ™‚

compact zinc
#

both bcrypt as well as argon2id includes salt, so this would prevent dictionary attacks.

night parrot
#

well, it makes dictionary attacks more expensive, yes.

compact zinc
#

yeah they would need to hash every try again

night parrot
#

argon is what I use for disk encryption passwords, but that's a different scenario since any attacker can get an encrypted disk image.

compact zinc
#

I have actually never used Argon2id for server-side password hashing. Only bcrypt.

#

but I'm using Argon2id for a password manager

night parrot
#

I also think it would make sense to automatically try typo-fixing passwords that don't match, as it's a lot easier to type a seventy-character password with one or two typos than to type a 30-character password correctly consistently...

compact zinc
#

I'm not checking if their password is already located in any password dictionary...

#

so basically as long as their password is brute-force "resistant" it is good.

night parrot
#

my current impression is expensive hashing only helps with medium-strength passwords. weak passwords are weak, strong passwords are strong even with cheap hashing

compact zinc
#

so are you using something like sha256, sha512... for cheap password hashing?

#

or even cheaper algo

night parrot
#

nothing cheaper than sha512 ๐Ÿ™‚

compact zinc
#

cool

night parrot
#

I think at this point I have a Pavlovian reaction to even typing the characters "m", "d", "5"

compact zinc
#

I usually use sha512 for bearer tokens

night parrot
#

anyway, if your default settings for argon2id aren't good or can't be changed as easily as they should be, please file an issue! It's important not to fall behind the security users are expecting, even if they're wrong to do so ๐Ÿ˜‰

compact zinc
#

not long time ago, I have been migrating a PHP website from version 5 to 8. The company did used md5 for password hashing Laughing_Facepalm

night parrot
#

soon it'll all be replaced by passkeys, whether we want that or not.

compact zinc
#

It would probably take another decade or more

#

as it is a lot faster for each programmer to implement passwords than passkeys

#

This is the best way in my opinion:

  • Something only you know (passwords)
  • Something only you have (Yubikey, fingerprint, mobile phone?...)
#

With passkeys people want to switch from (Something only you know - passwords) to (Something only you have - passkeys)