The shorter way of saying this is that you should not use libraries that expose "AES", but instead things like Sodium that expose "boxes" --- and, if you need to do things that Sodium doesn't directly expose, you need an expert.
Contra what other people on this thread have suggested, reasonable security engineers do not in fact believe that ordinary developers aren't qualified to build their own password forms or permissions systems; that's a straw man argument.
I haven't so much seen the latter kind of guy, the one saying you need a certified professional to safely output-filter HTML or whatever, but I see "lol block cipher modes whatever" people on HN all the time, on almost every thread about cryptography, dunking on anyone who says "don't roll your own cryptography".
I sort of assume that the latter is a random sampling of software engineers but the former is sampled specifically for idiots who YOLO their crypto.
If you're using minisign or Sigstore or whatever to do something like verify upstream dependencies, sure. But if you're building a system that is about some novel problem domain involving signatures: you should get an expert to verify your system. The trail of dead bodies here is long and bloody.
I think you have a pre-defined notion when I say the word "signature" about what I meant (perhaps something about software signing or SSL certificates). I literally meant "checking that HMAC-SHA-2 of some bytes is what you expect" (using a library for the hash). Incidentally, that's how you authenticate a JWT.
It's not only about the raw operation of checking bytes are equal (hopefully in a constant time manner, if applicable), but also about ensuring the desired security properties are actually present in the application!
- memcmp(actual_sig, expected_sig) == 0
- strcmp(actual_sig_base64, expected_sig_base64) == 0
- strcasecmp(actual_sig_base64, expected_sig_base64) == 0No, not "sure". minisign and Sigstore are completely different things, and one needs to understand how those work and what the corresponding signatures mean: A minisign signature says "the owner of that key signed this". Whoever the owner might be at the moment of signature. Whenever that might have happened.
A sigstore signature says "Google/Github/... says that this OpenID account generated that one key that the sigstore CA attests and that signed that blob at that time". Time/ordering verification is better than in minisign, because it is there, even if through a trusted party. But identity verification relies on a trusted third and fourth party, some of whom do have a history of botching their OpenID auth.
Those are not equivalent, and knowledge is needed to not mix them up. You don't need to be an expert, but you should try to understand as much as you can, identify what you don't understand and where you must trust an expert, implementation, company, recommendation or standard to be correct and trustworthy. And then decide if that is ok with you.
But once you adopt something like BCrypt or scrypt or PBKDF2 or Argon2 (literally throw a dart at a dartboard), you're into the space of things where you should, as a competent engineer, be expected to figure out a sound system on your own. The only domain-specific knowledge you really need given to you by a cryptographer is "don't just use salted hashes".
Simply replying "everyone knows what the problem is and has know since the 70s" is not as helpful as "here is the problem in three sentences".
You can assume a base level of knowledge in your answer, as I have worked in encryption for a little bit, having written the 3DES implementation for some firmware, and done EMV development.
You want something that is slow and takes a lot of resources to run. PBKDF2 was an early attempt which uses lots and lots of CPU, but it doesn’t use a lot of space. scrypt, bcrypt and Argon2 use lots of CPU and also lots of space in an attempt to make it more expensive to run (which is the point: you want the expected cost of finding a password to be more than the expected value of knowing that password).
That’s one level of issue.
The next level of issue is that when you run a simple system like that the user shares his password with you for you to check that it’s valid. This sounds find: you trust yourself, right? But you really shouldn’t: you make mistakes, after all. An you may have employees who make mistakes or who are malicious. They might log the password the user sent. They might steal the user’s password. Better systems use a challenge-response protocol in which you issue a one-time challenge to the user, the user performs some operation on the challenge and then responds with a proof that he knows his secret.
But those have their own issues! And then there are issues with online systems and timing attacks and more. It’s a very difficult problem, and it all matters what you are protecting and what your threat model is.
it's all a trade-off - those challenge/response systems are better, but they also have more moving parts. There's more bits in the system to go wrong.
When there's only two pieces of the system (compare salted hash of user-provided password against stored salted hash) there's very little room for errors to creep in. Your auditing will all be focused on ensuring that the user-provided password is not leaked/stored/etc after a HTTP sign-in request is rxed.
When using challenge/response, there is some pre-shared information that must synchronised between the systems (algorithm in use, key length, etc depending on the specific challenge/response system chosen). That's a great deal more points of attacks for malicious actors.
And then, of course, you need to add versioning to the system (algorithms may be upgraded over time, key lengths may be expanded, etc) that all present even more points of attack.
Compare to the simple "compare salted hash of password to stored salted hash": even with upgrading the algorithms and the key lengths there still remain only one point of attack - downloading the salted hashes!
It doesn't matter how much more secure a competing system is if it introduces, in practice anyway, more points of failure.
My takeaway after doing some cryptography for some parts of my career is that by choosing a hash function to be expensive in computational power and expensive is space and keeping the "user enters a password and we verify it" is still going to have fewer points of attack than "Synchronising pre-shared information between user and authenticator as the security is upgraded over the years".
Basically, the trade-off is between "we chose a quick hash function, but can at least upgrade everything without the client software noticing" and the digital equivalent of "It's an older code, sir, but it checks out" problems.
> you need to add versioning to the system
You need this with salted hashes, too! And of course with any password-based system.
Okay, I read it, and then re-read it. I still don't get why (for example) `bcrypt` (a salted hash function) is a bad idea.
I fully accept that I am missing something here, but I really would like to know why using `bcrypt` is a problem.
>> you need to add versioning to the system
> You need this with salted hashes, too! And of course with any password-based system.
Not in the client software, you don't. The pre-shared information with password-based system is generally stored in the users head.
The pre-shared information in the challenge/response system means both the submitting software (interacting with the user and rxing the challenge) as well as the receiving software (txing the challenge and rxing the response) need to be synchronised.
Now, once again, I fully accept that I might be missing something here, but AFAIK, that synchronisation contains extra points of attacks; points of attacks that don't exist in the password/salted-hash system.
And since absolutely no system ever discards existing mechanisms completely when upgrading, that deprecated but still supported for a few more months is even more additional points of attack.
Once again, I am trying to understand, not be contentious, and I want to fuolly understand:
a) The problem with salted hashes like `bcrypt`
b) What changes need to be made to client software when upgrading algorithms and key lengths in a password-based system.
What you can't do is use SHA2 with a random salt and call it a day.
I believe the actual implementation gives two output fields as a single value, with that value containing the salt and the hash.
This might be why we appear to be talking past each other - I consider bcrypt to be a salted hash because it takes a salt in the inputs and produces a hash in the output.
The fact that the output also contains the salt is, in my mind, an implementation detail.
You'll avoid this confusion in the future if you don't refer to bcrypt as a "salted hash". Salted hashes are the technology bcrypt was invented to replace.
The distinction between a KDF and a hash function is that a KDF has an output length that is configurable so you can directly use its output to generate a cryptographic key with 100% of the entropy of the input. A KDF theoretically needs to have arbitrary input length and arbitrary output length. Bcrypt has neither.
Argon2d is great, scrypt is great, PBKDF2 with 1000000+ rounds works fine if you want FIPS, but bcrypt is somehow still on many people's list as though it doesn't have these flaws. It's not at the point where it needs to be imminently replaced in a legacy system, but if you're building something new, you should use something better.
This is a terrible stopgap solution, in 1975 it's reasonable to say we have other priorities right now, we'll get to that later. But it's very silly that in 2025 you're telling people to try PBKDF2 when really they shouldn't even be in this mess in the first place.
Remember when Unix login still hadn't solved this? 1995. That was last century. And yet, here we are, still people are writing "Password" prompts and thinking they're doing a good job.
Proper password hashing is protecting against a different threat than blocking brute force login attempts (which isn't really an encryption issue.) Password hashing is more about protecting your user's passwords if your database is breached so that you limit the attackers ability to re-user those credentials to access other services the user may have re-used them with.
Reasonable developers are qualified to do those things. But to build a full-featured authentication subsystem for their webapp? If it's something that holds any kind of reasonably private info, I'm not so sure.
Sure, a reasonable developer will use some sort of PBKDF to hash passwords. But when users need a password reset over email, will they know not to store unhashed reset tokens directly in the database? Will they know to invalidate existing sessions when the user's password is reset? Will they reset a browser-stored session ID at login, preventing fixation? And on and on and on. The answer to some of these questions will be yes, but most developers will have a few for which the answer is no. Hell, I've probably built more auth systems than most (and have reported/fixed a few vulnerabilities on well-known open-source auth systems to boot) and I'm honestly not sure I'd trust myself to do it 100% correctly for a system that really mattered.
Even outside of "holding the crypto wrong", these things have sharp edges and the more you offload to an existing, well-vetted library the more likely you are to succeed.
In short, in at least one variation, the attacker is able to smuggle in a known (unauthenticated) session token into the victims browser. Once the victim logs in the session token is authenticated and known to the attacker.
The easy countermeasure is to renew the session token on login and not reuse a previously unauthenticated session token. Or your application has no session at all before login.
When I rolled my eyes up, I soon got a termination.
Amount of foot guns in auth flow is high. Implementation of login / password form is just a small piece.
Making sure there are no account enumeration possibilities is hard. Making sure 2FA flow is correct and cannot be bypassed is hard. Making proper account recovery flow has its own foot guns.
If you can use off the shelf solution where someone already knows about all those - it still stands don’t roll your own.
I've made one of these goofs myself when I was much younger (32 byte tokens, attacker can snap them into two 16-byte values, replace either half from another token and that "works" meaning the attacker can take "Becky is an Admin" and "Jimmy is a Customer" glue "y is an Admin" to "Jimm" and make "Jimmy is an Admin") which got caught by someone more experienced before it shipped to end users, but yeah, don't do that.
The issue with security researchers, as much as I admire them, is that their main focus is on breaking things and then berating people for having done it wrong. Great, but what should they have done instead? Decided which of the 10 existing solutions is the correct one, with 9 being obvious crap if you ask any security researcher? How should the user know? And typically none of the existing solutions matched the use case exactly. Now what?
It's so easy to criticize people left and right. Often justifiably so. But people need to get their shit done and then move on. Isn't that understandable as well?
This is plain incorrect in my experience.
Recommended reading (addresses the motivations and ethics of security research): https://soatok.blog/2025/01/21/too-many-people-dont-value-th...
> Great, but what should they have done instead? Decided which of the 10 existing solutions is the correct one, with 9 being obvious crap if you ask any security researcher?
There's 10 existing solutions? What is your exact problem space, then?
I've literally blogged about tool recommendations before: https://soatok.blog/2024/11/15/what-to-use-instead-of-pgp/
I'm also working in all of my spare time on designing a solution to one of the hard problems with cryptographic tooling, as I alluded to in the blog post.
https://soatok.blog/2024/06/06/towards-federated-key-transpa...
Is this not enough of an answer for you?
> How should the user know? And typically none of the existing solutions matched the use case exactly. Now what?
First, describe your use case in as much detail as possible. The closer you can get to the platonic ideal of a system architecture doc with a formal threat model, the better, but even a list of user stories helps.
Then, talk to a cryptography expert.
We don't keep the list of experts close to our chest: Any IACR-affiliated conference hosts several of them. We talk to each other! If we're not familiar with your specific technology, there's bound to be someone who is.
This isn't presently a problem you can just ask a search engine or generative AI model and get the correct and secure answer for your exact use case 100% of the time with no human involvement.
Finding a trusted expert in this field is pretty easy, and most cryptography experts are humble enough to admit when something is out of their depth.
And if you're out of better options, this sort of high-level guidance is something I do offer in a timeboxed setting (up to one hour) for a flat rate: https://soatok.com/critiques
Do you happen to know of a similar resource applicable to common HN deployment scenarios, like regular client-server auth?
For example, in your Beyond Bcrypt blog post[0] you seem to propose hand-writing a wrapper around bcrypt as the best option for regular password hashing. Are there any vetted cross-language libraries which take care of this? If one isn't available, should I risk writing my own wrapper, or stick with your proposed scrypt/argon2 parameters[1] instead? Should I perhaps be using some kind of PAKE to authenticate users?
The internet is filled with terrible advice ("hash passwords, you can use md5"), outdated advice ("hash passwords, use SHA with a salt"), and incomplete advice ("just use bcrypt") - followed up by people telling you what not to do ("don't use bcrypt - it suffers from truncation and opens you up to DDOS"). But to me as an average programmer, that just leave behind a huge void. Where are the well-vetted batteries-included solutions I can just deploy without having to worry about it?
[0]: https://soatok.blog/2024/11/27/beyond-bcrypt/
[1]: https://soatok.blog/2022/12/29/what-we-do-in-the-etc-shadow-...
You whole-heartedly recommend sigstore, a trusted-third-party system which plainly trusts the auth flows of the likes of Google or Github. It is basically a signature by OpenID-Login. This is no better than just viewing everything from github.com/someuser as trusted. The danger of key theft is replaced by the far higher danger of account theft, password loss and the usual numerous auth-flow problems with OpenID.
Why should I take those recommendations seriously?
I feel like you have to tell people to not roll your own whatever because there are so many of these types of people.
The form of this that bothers me the most is in infra (the space I work in). K8s is challenging when things go sideways, because it’s a lot of abstractions. It’s far more difficult when you don’t understand how the components underpinning it work, or even basic Linux administration. So there are now a ton of bullshit AI products that are just shipping return codes and error logs out to OpenAI, and sending it back rephrased, with emoji. I know this is gatekeeping, and I do not care: if you can’t run a K8s cluster without an AI tool, you are not qualified to run a K8s cluster. I’m not saying don’t try it; quite the opposite: try it on your own, without AI help, and learn by reading docs and making mistakes (ideally not in prod).
Registration, 2FA, reset, email verification, federation, password rules, brute force protection, RBAC/ABAC etc
(I'm no fan of Auth0 fwiw)