Only partially. If the client and server have an agreement on a hashing protocol, there’s no reason that the browser shouldn’t be able to hash as well and prevent the password from ever leaving memory on the client system. HTTPS is still vulnerable to many man in the middle attacks, and many corporate and business networks do deep packet inspection to decrypt https (they control the machines so intercepting the cert issuance and installing their own root CA is easily doable). Another issue is that improper logging on the server (or depending on the implementation, even intermediate load balancers) could accidentally leak the plaintext password. Client side hashing is a much better solution to this, and if it’s a native browser supported protocol, it would even work without JS.
What does it matter? If a criminal gains the hash, they can log in and be malicious anyway. If a criminal can do a MitM, they can substitute the Javascript that's hashing your password with all the nonces and salts and peppers you add to it and send the password anyway.
If you just hash the password, the hash becomes the password. You're not solving the problem that way, you're just switching around definitions.
There is an alternative that's supported in most platforms, and that's WebAuthn. Not even a need for a password automatic secure handshakes and with the right protocols, the keys differ per site so they're very hard to spoof through phishing. You can achieve much of the same thing with client TLS certificates, but the UX for that is even worse than the UX for HTTP Basic.
But the attacker controls the TLS connection already, so they'll just strip out the hashing functionality or send a piece of JS to steal the password directly from the password field.
It's not that I don't understand where you're coming from (I once almost started writing such a library a few years back!), but I just can't think of a threat model where this makes sense. That's also what moved me away from working on such a system.
To me, this approach feels like an attempt to recreate software-only U2F, but outside the browser. I don't think client side code can fix these problems. It can make stealing passwords more difficult for criminals if every website uses their own bespoke password processing script, but that'll also add a huge attack surface to your code and it'll be a burden to maintain.
I don't know why people are assuming the attacker:
A) Controls the code running the auth API
B) Controls the javascript
As if that's the common attack.
The common attack is that the attacker has a read on the hash, either through injection vulnerabilities or other leaks.
Your attacker, as described, has remote code execution on a server that hosts the auth API and the Javascript in one place. That is a very specific, powerful attacker!
> but I just can't think of a threat model where this makes sense
1. An attacker has CSRF and can trick your code into sending the hash to them. This significantly reduces the harm of that attack.
2. An attacker has an injection vulnerability giving them read access to hashes ie: sql injection, perhaps the most significant and relevant attack to discuss with passwords.
3. An attacker takes advantage of a timing attack, bruteforcing the server and measuring response times to leak its value. They can now only leak the hashed value, which is going to take way longer to leak and doesn't expose their password.
And more!
This defense tackles what are very very arguably the major threats to consider with regards to password security (they're the big ones in OWASP top 10).
> It can make stealing passwords more difficult for criminals if every website uses their own bespoke password processing script, but that'll also add a huge attack surface to your code and it'll be a burden to maintain.
You can significantly reduce harm and attack surface with all of 3 lines in your frontend code.
salt = hash(username + static_salt)
password_hash = pbkdf2(plaintext_password, salt)
it's trivial to implement this.
I'll grant you that PAKEs may be more complex to implement today, but even if we talk about a very basic implementation of client side hashing I think there's obvious value.
The attacker I describe has MITM access. The attacker can read and modify page contents, but nothing on the server side. This can only be achieved by malware on the client side or tomfoolery with certificates, so it's not a common attack for sure. But, breaking basic passwords sent over a secure channel aren't a common attack in general.
If you presume the attacker can only read the data transmitted but cannot alter it, your system might work, but I'm not sure in what scenario a hacker can break HTTPS secrecy without also being able to modify the contents of traffic over the wire.
A CSRF vulnerability won't let you send the password to a random host, unless you have full arbitrary code execution (in which case your protections don't make sense either) or if your auth code is unrealistically buggy (letting the attacker embed secrets in a resource somehow).
The database already contains hashes for normal password auth, so I'm not sure why your system would be any better. The password database isn't stored client side, after all, and I hope nobody is still storing passwords in plaintext.
I'm not sure why an attacker would be able to guess the hash from a timing attack, if they can do that then the hashing implementation is very flawed, to the point you just shouldn't be hashing passwords with it.
Your custom salt/hashing system solves password reuse I suppose, but it doesn't add any protections to your website while adding complexity at your cost. For your website, you just changed the way the password looks (which is the hash, not the direct input) at the cost of needing Javascript execution.
In my opinion, your login page would be a lot more secure with a CSP that disallows all scripting, just in case, and uses a simple system that's easy to spot mistakes in, like HTTPS POST or Basic auth.
> The attacker I describe has MITM access. The attacker can read and modify page contents, but nothing on the server side. This can only be achieved by malware on the client side or tomfoolery with certificates, so it's not a common attack for sure. But, breaking basic passwords sent over a secure channel aren't a common attack in general.
I don't think that attacker is worth dealing with. At that point you're outside of the security responsibilities of a website and it's the operating systems job to provide security.
> But, breaking basic passwords sent over a secure channel aren't a common attack in general.
Isn't it? Lots of passwords are sent over TLS but SQL injection is still a top 10 vulnerability.
> a hacker can break HTTPS secrecy without also being able to modify the contents of traffic over the wire.
I'm not implying that they can. I'm implying they can read the hashed values after being transmitted to the server.
So attacker in the following positions, at least:
1. Owns your auth endpoint (but not your CDN)
2. Owns your proxy
3. Has read access to your logs and the password is in those logs
4. Has a SQL injection or timing attack against your password auth/ db
> A CSRF vulnerability won't let you send the password to a random host, unless you have full arbitrary code execution
Yeah true, I was thinking about the auth token, not the password.
> I'm not sure why an attacker would be able to guess the hash from a timing attack, if they can do that then the hashing implementation is very flawed,
Timing attacks aren't a property of the hash but of the operations on that hash.
> The database already contains hashes for normal password auth, so I'm not sure why your system would be any better.
Do you mean that the database would be doing server side hashing regardless? That's true (I sure hope). But the attacker will have to brute force a much larger space to recover the plaintext password and the point of doing the client side hashing is to protect other sites if a user reuses their password on those sites.
> Your custom salt/hashing system solves password reuse I suppose
To be clear, that's the point, and I don't think that's small. It's about reducing harm to your users - even if within the scope of your website your user is still vulnerable you are protecting them within the scope of other websites.
I haven't made this argument yet, but I believe it also adds security elsewhere by distributing the cost of your hashing to clients. Your server has to handle N clients, and maybe needs to response in 5ms to each client - so it can spend, say, 3ms on hashing. To keep your tail latencies down you might try to do 1ms of hashing.
But you don't have to worry about your compute if you push it to the client. You can have the client perform, say, 1M rounds of PBKDF2.
So the attacker has two choices.
1. Brute force the client hash, which is 32bytes and really not feasible. That is to say that in a naive brute force they start with 32bytes 0'd out and start counting until they reach your hash - not fun.
2. Brute force the client password, which has a huge number of rounds that you could never get away with on your server. 1M rounds of pbkdf2 is not something you'd want to do on your server but distributed across your clients it's no problem at all - a few hundred milliseconds perhaps. But that's devastating to an attacker trying to recover the plaintext - of course, with some caveats (a relatively weak salt).
I haven't put enough thought into this benefit to claim it, but I may as well throw it out there.
> but it doesn't add any protections to your website while adding complexity at your cost.
It's very little complexity at very little cost. It's a few lines of code that execute at the edge - you pay nothing for those cycles.
> at the cost of needing Javascript execution.
I don't consider this a cost. I think it's totally ridiculous to say that JS execution as a requirement is a "cost" - the vast majority of websites require JS, and there are plenty of good reasons for it (like telling your user their password is too short). If you care so much about JS as a cost, ok, don't do client side hashing.
> In my opinion, your login page would be a lot more secure with a CSP that disallows all scripting, just in case, and uses a simple system that's easy to spot mistakes in, like HTTPS POST or Basic auth.
I disagree. A CSP would be an excellent thing to implement, and everyone should do so. But I don't think that completely denying script execution is a good idea - your users are far more vulnerable to using weak and/or reused passwords than XSS on a site with the minimal scripting necessary to implement this code (no 3rd party packages are required for the code I mentioned).
Your suggestion is basically going back to the days where databases stored plaintext passwords in the database, just that the plaintext happens to be a hash.
> If the client and server have an agreement on a hashing protocol, there’s no reason that the browser shouldn’t be able to hash as well and prevent the password from ever leaving memory on the client system.
Shouldn’t it be a proper challenge/response? Otherwise the hash is barely better than the password.
There are answers to this. I asked myself this a few years ago, which led recursively to new tricky questions. I eventually solved the tree of the questions: I designed myself into a solution that I later realized was essentially an asymmetric PAKE (e.g., analogous to OPAQUE).
So, an attempt to solve your question eventually leads to using an asymetric PAKE. But, this is way, way more complex. You'd really have to squint your eyes to believe that the diminishing returns are worth it.