TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Okta Bcrypt incident lessons for designing better APIs

377 pointsby n0rdy3 months ago

25 comments

tptacek3 months ago
Bcrypt is a password hash, not a KDF, which is the way it was used in this API. It&#x27;s super unclear to me why they wanted a string-based KDF here at all; does anyone have more context?<p>I&#x27;ve in the past been annoying about saying I think we should just call all password hashes &quot;KDFs&quot;, but here&#x27;s a really good illustration of why I was definitely wrong about that. A KDF is a generally-useful bit of cryptography joinery; a password hash has <i>exactly one job</i>.
评论 #42955861 未加载
评论 #42957300 未加载
评论 #42955709 未加载
评论 #42960267 未加载
评论 #42959763 未加载
评论 #42960508 未加载
评论 #42956119 未加载
评论 #42964743 未加载
jhhh3 months ago
I can see the incident was a jumping off point to talk about bad APIs (bcrypt probably should error &gt;72) but it sounds like the actual bug was they weren&#x27;t checking the value in the cache matched the data they used in the hash for the key. The authentication cache check should survive any arbitrarily bad hashing algorithm because all of them are going to have collisions (pigeonhole principal). Even an arbitrarily &#x27;strong&#x27; hash function with no input truncation, as long as it has a fixed width result, will have this property. Thus, any arguing in the comments here about different hash functions with different truncation properties is moot.<p>The analogy is something like creating a hash map whose insert function computes the slot for the key and <i>unconditionally</i> puts the value there instead of checking if the keys are the same during a collision. No amount of tinkering with the hash function fixes this problem. The algorithm is wrong. A hashmap should survive and be <i>correct</i> even giving it a hash function that always returns 4.
评论 #42958355 未加载
评论 #42958024 未加载
评论 #42957688 未加载
评论 #42965010 未加载
nfriedly3 months ago
I strongly agree with the conclusion that the libraries should reject input they can&#x27;t correctly handle instead of silently truncating it.<p>I co-maintain a rate-limiting library that had some similar rough edges, where it wouldn&#x27;t always be obvious that you were doing it wrong. (For example: limiting the IP of your reverse proxy rather than the end user, or the inverse: blindly accepting any X-Forwarded-For header, including those potentially set by a malicious user.) A couple years back, I spent some time adding in runtime checks that detect those kinds of issues and log a warning. Since then, we&#x27;ve had a significant reduction in the amount of not-a-bug reports and, I assume, significantly fewer users with incorrect configurations.
评论 #42964431 未加载
sandeepkd3 months ago
I enjoyed the article and the detailed analysis for different languages. The conclusion is probably the part where most of the disagreement lies. API design is is not really at fault here if we consider the purpose of the API and the intended output.<p>The API was designed to generate a hash for a password (knowledge factor) and for performance and practical reasons a limit has been picked up (72). The chances that some one knows your first 72 characters of password implies that the probably is a lot higher for the abuser to have remaining characters too.<p>While smaller mistake here in my opinion was not knowing the full implementation details of a library, the bigger mistake was trying to use the library to generate hash of publicly available&#x2F;visible information
评论 #42970029 未加载
评论 #42962091 未加载
renewiltord3 months ago
This is a completely unreasonable API. It reminds me of the `mysql_real_escape_string` vs. `mysql_escape_string`. The default API must be the strict one. You should be able to configure it to be broken but silent truncation is an insane piece of functionality. There is no universe in which this is logical. One might as well just have everything return void* and then put in the documentation what type to cast to. The invariant is clearly a historical accident.<p>As a mistake, it&#x27;s fine. Everyone writes up things like that. But defending it as an affirmatively good decision is wild.
评论 #42960249 未加载
veqq3 months ago
[u&#x2F;forgot-CLHS](<a href="https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;lisp&#x2F;comments&#x2F;1ikrz1g&#x2F;shout_out_to_common_lisps_ironclad&#x2F;" rel="nofollow">https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;lisp&#x2F;comments&#x2F;1ikrz1g&#x2F;shout_out_to_...</a>) notes that Common Lisp&#x27;s defact standard cryptography library Ironclad&#x27;s [implementation](<a href="https:&#x2F;&#x2F;github.com&#x2F;sharplispers&#x2F;ironclad&#x2F;blob&#x2F;master&#x2F;src&#x2F;kdf&#x2F;bcrypt.lisp">https:&#x2F;&#x2F;github.com&#x2F;sharplispers&#x2F;ironclad&#x2F;blob&#x2F;master&#x2F;src&#x2F;kdf...</a>) avoids such problems!<p>``` (defmethod derive-key ((kdf bcrypt) passphrase salt iteration-count key-length) (declare (type (simple-array (unsigned-byte 8) (*)) passphrase salt)) (unless (&lt;= (length passphrase) 72) (error &#x27;ironclad-error :format-control &quot;PASSPHRASE must be at most 72 bytes long.&quot;))...) ```
ww5203 months ago
Have they did a bcrypt(password + userId + username), it won&#x27;t be so bad. Order of entropy is important.<p>Also I&#x27;m not sure what functionality the authentication cache provides, but their use of bcrypt(userId + username + password) implies the password is kept around somewhere, which is not the best practice.<p>OT. Has Argon2 basically overtaken Bcrypt in password hashing in recent years?
评论 #42957878 未加载
jedisct13 months ago
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).
评论 #42955552 未加载
评论 #42955669 未加载
评论 #42955690 未加载
coolgoose3 months ago
I am curious why bcrypt was used for hashing in the first place and not something like sha-512<p>Is there a reason I might be missing?
评论 #42955603 未加载
评论 #42960657 未加载
评论 #42955637 未加载
thekemkid3 months ago
In Node, you would commonly reach for the builtin core &quot;node:crypto&quot; module to run cryptographic functionality like this. I wondered why that wasn&#x27;t used here, but bcryptjs was. After digging into it a little, node doesn&#x27;t ship with core support for bcrypt, because it&#x27;s not supported by OpenSSL.<p>The node crypto module is essentially an API that offloads crypto work to OpenSSL. If we dig into OpenSSL, they won&#x27;t support bcrypt. Bcrypt won&#x27;t be supported by OpenSSL because of reasons to do with standardisation. <a href="https:&#x2F;&#x2F;github.com&#x2F;openssl&#x2F;openssl&#x2F;issues&#x2F;5323">https:&#x2F;&#x2F;github.com&#x2F;openssl&#x2F;openssl&#x2F;issues&#x2F;5323</a><p>Since bcrypt is not a &quot;standardised&quot; algorithm, it makes me wonder why Okta used it, at all?<p>I remember in uni studying cryptography for application development and even then, back in 2013, it was used and recommended, but not standardised. it says a lot that 12 years on it still hasn&#x27;t been.
whalesalad3 months ago
damn that sounds like a rookie mistake for an organization who is literally in the business of secure auth
progmetaldev3 months ago
Reminds me of when I saw a junior developer calling SHA-1 on an incrementing integer ID, with no salt. We had a long talk about it, he thought it was too &quot;scrambled&quot; to allow anyone to recognize what was being done. He shouldn&#x27;t have been so junior, he was 4 or 5 years into his career. I had to be the bad guy and override his decision without further discussing why it was a bad idea, and I really tried for a good 45 minutes to explain things. He got it a week later when I showed him rainbow tables, and I felt bad having to tell him to just do what I said for the solution, but sometimes you just have to make the decision to say &quot;do what I said, I&#x27;m sorry you don&#x27;t understand, I tried to explain.&quot;
评论 #42958562 未加载
评论 #42959994 未加载
评论 #42957374 未加载
Tostino3 months ago
That is such a rookie mistake. It&#x27;s not some hidden information that bcrypt has a 72 char limit. Pretty widely documented in multiple implementations and languages.<p>How does a company whose only job is security screw that up so badly?
评论 #42958744 未加载
评论 #42955863 未加载
评论 #42957553 未加载
评论 #42956333 未加载
jmuguy3 months ago
I was curious how bcrypt-ruby would handle this. It does not throw an error for input length larger than 72. However the actual API for the gem makes it pretty clear its for hashing a password, and not just hashing in general - as you can see from the code.<p><a href="https:&#x2F;&#x2F;gist.github.com&#x2F;neontuna&#x2F;dffd0452d09a0861106c0a46669a3ff0" rel="nofollow">https:&#x2F;&#x2F;gist.github.com&#x2F;neontuna&#x2F;dffd0452d09a0861106c0a46669...</a>
underdeserver3 months ago
Hold on, in the Rust example, how does `err_on_truncation` get set? TFA completely ignored that there&#x27;s a setting somewhere (probably incorrectly defaulting to false)
评论 #42960190 未加载
评论 #42960005 未加载
mariocesar3 months ago
I&#x27;m confused, it seems that the OP wants to use Bcrypt as an encoding&#x2F;decoding utility.<p>About solutions, Django hashes by default the password only with a salt. I&#x27;m not sure why it would be valuable to combine user_id+username+password. I&#x27;ve always assumed that using salt+password was the best practice.
评论 #42962985 未加载
sscarduzio3 months ago
what I would have naturally done without anticipating any flaw (and probably be just OK):<p><pre><code> cache_key = sha(sha(id + username) + bcrypt(pass)) </code></pre> with sha256 or something.
评论 #42960067 未加载
SebFender3 months ago
I&#x27;ve seen this multiple times - even better I don&#x27;t know how many ways we found a simple workaround or bypass of the complete process in so many apps... In essence this has nothing to do with the API itself but the way in which is another ballgame altogether. Great post though.
bawolff3 months ago
&gt; On the other hand, such long usernames are not very usual, which I agree with<p>Weird take. Usernames are often chosen by the user. Less so in corporate world but definitely not unheard of
评论 #42957648 未加载
Lvl999Noob3 months ago
Can someone explain, in clear layman terms, what the difference is between a password hash and a KDF? I have went through this whole thread and tried to look around online but I still don&#x27;t understand.
评论 #42961153 未加载
评论 #42961155 未加载
zero_k3 months ago
Another incident at Okta? Oh no! Its security has _always_ been a mess. It&#x27;s a dumpster fire and no client of their cares because their identity systems are so messed up, that it&#x27;s better to have the mess of Okta, than the mess they are sitting on. It&#x27;s kinda crazy they get away with such incredibly bad security practices. Like... this bcrypt issue has been know for a LONG while. We used to test for it 8-10 years ago.<p>There&#x27;s either (1) nobody competent enough there to know (which is likely not true, I had a pentester friend recently join, and she is very good), or, more likely (2) management doesn&#x27;t care and&#x2F;or doesn&#x27;t give enough authority to IT security personnel.<p>As long as clients don&#x27;t have any better options, Okta will stay this way.
评论 #42981817 未加载
withinboredom3 months ago
I&#x27;m really surprised they didn&#x27;t cover PHP since (almost?) every framework uses bcrypt in php these days.
评论 #42967526 未加载
philippta3 months ago
What&#x27;s the reason behind bcrypt(userId + username + password) rather than just bcrypt(password) ?
评论 #42960863 未加载
评论 #42960827 未加载
llmthrow1023 months ago
How does anyone take Okta seriously after this incident btw?
nabla93 months ago
&gt; was used to generate the cache key where we hash a combined string of userId + username + password.<p>Don&#x27;t conceive your own cryptographic hacks. Use existing KDF designed by professionals.
评论 #42957384 未加载
评论 #42962036 未加载
评论 #42957359 未加载