Live data from Hacker News

Okta Bcrypt incident lessons for designing better APIs

n0rdy.foo

21–30 of 169 posts

Re: Okta Bcrypt incident lessons for designing better APIs

#21

Earlier quoted context omitted.

They didn't want a KDF, as far as I know, but they wanted a hash function with unlimited input size. Including the username in the hash input gives you guaranteed domain separation between users that you don't get from salts/nonces. Its a generally good idea if you have a hash function with unlimited input size (all modern cryptographic hash functions except bcrypt have unlimited input size).

They clearly wanted something stronger than "a hash function" or they'd have reached for weaker cryptographic hashes.

They wanted a hard-to-compute cryptographic hash function. Today, that means bcrypt or something with a KDF construction. However, they needed one with unlimited input size, which rules out bcrypt.

Re: Okta Bcrypt incident lessons for designing better APIs

#22
post #16

Earlier quoted context omitted.

But why not bcrypt the password, but sha the cache key on top?

I guess because they didn't anticipate this flaw.

Also prehashing opens you up to an other bcrypt flaw you need to be aware of: it stops at the first NUL byte, so you need to use some sort of binary-to-text encoding on top of the hash to ensure you don't have any of those in the data you ultimately hand off to bcrypt.

Re: Okta Bcrypt incident lessons for designing better APIs

#23
post #4
post #2

The bcrypt implementation in the Zig standard library has both the bcrypt() function (where truncation is explicitly documented) and the bcryptWithoutTruncation() function (which is recommended and automatically pre-hashes long passwords).

Author here: thanks for reading the post. It's great to hear that Zig covered both cases. However, I'd still prefer the opposite behavior: a safe (without truncation) default `bcrypt()` and the unsafe function with the explicit name `bcryptWithTruncation()`. My opinion is based on the assumption that the majority of the users will go with the `bcrypt()` option. Having AI "helpers" might make this statistic even worse…

`bcrypt()` is bcrypt as implemented everywhere else, and is required for interoperability with other implementations. If you don't truncate, this is not `bcrypt` any more.

`bcryptWithTruncation()` is great for applications entirely written in Zig, but can create hashes that would not verify with other implementations.

The documentation of these functions is very explicit about the difference.

The verification function includes a `silently_truncate_password` option that is also pretty explicit.

Re: Okta Bcrypt incident lessons for designing better APIs

#24

Earlier quoted context omitted.

They clearly wanted something stronger than "a hash function" or they'd have reached for weaker cryptographic hashes.

They wanted a hard-to-compute cryptographic hash function. Today, that means bcrypt or something with a KDF construction. However, they needed one with unlimited input size, which rules out bcrypt.

Or just a hash of the bcrypt hash, for the password!

I don't like using thought-stopping cliches any more than anybody else does, but this design feels a little cargo-culted. All this stuff follows the more fundamental question of "why is the password mixed into a cache key"?

Re: Okta Bcrypt incident lessons for designing better APIs

#25
post #24

Earlier quoted context omitted.

They wanted a hard-to-compute cryptographic hash function. Today, that means bcrypt or something with a KDF construction. However, they needed one with unlimited input size, which rules out bcrypt.

Or just a hash of the bcrypt hash, for the password! I don't like using thought-stopping cliches any more than anybody else does, but this design feels a little cargo-culted. All this stuff follows the more fundamental question of "why is the password mixed into a cache key"?

Yeah, I think both of the following would have worked if they wanted the password involved in a cache key and they wanted bcrypt to be used:

* bcrypt(SHA-512(PW || stuff))

* SHA(stuff || bcrypt(PW))

Disclaimer: Not cryptography advice.

It's still unclear to me why the password is in there.

Re: Okta Bcrypt incident lessons for designing better APIs

#26

Earlier quoted context omitted.

They clearly wanted something stronger than "a hash function" or they'd have reached for weaker cryptographic hashes.

They wanted a hard-to-compute cryptographic hash function. Today, that means bcrypt or something with a KDF construction. However, they needed one with unlimited input size, which rules out bcrypt.

bcrypt-pbkdf (used in OpenSSH) exists for that purpose.

Re: Okta Bcrypt incident lessons for designing better APIs

#27
post #16

Earlier quoted context omitted.

I guess because they didn't anticipate this flaw.

Also prehashing opens you up to an other bcrypt flaw you need to be aware of: it stops at the first NUL byte, so you need to use some sort of binary-to-text encoding on top of the hash to ensure you don't have any of those in the data you ultimately hand off to bcrypt.

Thank you

Re: Okta Bcrypt incident lessons for designing better APIs

#28
post #24

Earlier quoted context omitted.

Or just a hash of the bcrypt hash, for the password! I don't like using thought-stopping cliches any more than anybody else does, but this design feels a little cargo-culted. All this stuff follows the more fundamental question of "why is the password mixed into a cache key"?

Yeah, I think both of the following would have worked if they wanted the password involved in a cache key and they wanted bcrypt to be used: * bcrypt(SHA-512(PW || stuff)) * SHA(stuff || bcrypt(PW)) Disclaimer: Not cryptography advice. It's still unclear to me why the password is in there.

For 'unlimited' input size it should be SHA-3-512. Maybe too slow, but Bcrypt is slower, right? Less things to go wrong too.

Re: Okta Bcrypt incident lessons for designing better APIs

#29
post #3

Bcrypt is a password hash, not a KDF, which is the way it was used in this API. It's super unclear to me why they wanted a string-based KDF here at all; does anyone have more context? I've in the past been annoying about saying I think we should just call all password hashes "KDFs", but here's a really good illustration of why I was definitely wrong about that. A KDF is a generally-useful bit of cryptography joinery;…

I believe there have been earlier protocols where the user’s secrets were used as a KDF to generate credentials in such a way that the server never sees the user’s password.

I’m wondering if okta was inspired by those.

Re: Okta Bcrypt incident lessons for designing better APIs

#30
post #24

Earlier quoted context omitted.

Or just a hash of the bcrypt hash, for the password! I don't like using thought-stopping cliches any more than anybody else does, but this design feels a little cargo-culted. All this stuff follows the more fundamental question of "why is the password mixed into a cache key"?

Yeah, I think both of the following would have worked if they wanted the password involved in a cache key and they wanted bcrypt to be used: * bcrypt(SHA-512(PW || stuff)) * SHA(stuff || bcrypt(PW)) Disclaimer: Not cryptography advice. It's still unclear to me why the password is in there.

hmac-bcrypt solves that problem very well, and should replace plain bcrypt: https://github.com/epixoip/hmac-bcrypt
Post reply on HN