9

Note: I've already read Is it ok to send plain-text password over HTTPS? and https security - should password be hashed server-side or client-side?, but here it's about a specific replacement method (see below).


After reading an article about a new authentication method on the Cloudflare blog, I looked at the POST requests that are sent while authenticating with "Developer Tools > Network". Many popular websites (Reddit, HN, etc.) still send the password in plaintext in the (SSL-secured) POST request (see screenshot below).

Is this login method still industry standard?

Is the following alternative more secure than sending plaintext password over HTTPS?

  • signup: client generates a random salt, and sends the tuple (username, salt, hash(plain_password + salt)) via a POST request. Then the plaintext password never reaches the server.

  • subsequent logins: the server has to send salt back to any client who tries to login with a given username, so that the client can hash with the same salt. So this means that the server is disclosing salt to anyone trying to log in with a given username.

  • benefit: the server stores a salted+hashed password (which is standard) but also the server has never, ever, seen the plaintext password even once (so if the server is compromised, the risk is limited)

Notes:

  • since H = hash(plain_password + salt) now behaves a little bit like a new plaintext (see the 2nd answer of Zero Knowledge Password Proof: why is hashing the password on the client side not a ZKP?), then the sever can store (username, salt, server_salt, hash(H + server_salt)) in the database, instead of (username, salt, H).

  • to mitigate risks for replay attacks, the server can also send a unique nonce, along with salt for each login, that expires after one login attempt

  • the main goal here is that the server never ever has access to the plaintext password or a simple hash of it (that could be often reversed with a single rainbow table for the whole site). I'm ok with the risk that an attacker has to compute one rainbow table per user.

  • Example attack I'd like to mitigate: if the server has access to a plaintext password and it is compromised (example Spectre / Meltdown vuln.) then the user's plaintext password (possibly reused on other websites) could be stolen, before it is salted-hashed and saved to the database.


enter image description here

6
  • The screenshot does not add any value to your question. I'd suggest to remove it.
    – mentallurg
    Jan 2 at 6:53
  • 2
    @mentallurg It just emphasizes that industry standard still seems to be "send plaintext passsword over HTTPS" (with the example of a very popular website).
    – Basj
    Jan 2 at 6:55
  • 9
    In some cases a screenshot can help. But in this case it doesn't. It makes the question bigger as needed. Additional screenshot makes impression that it provides additional important information, which is not the case. Thus the reader will be confused because they will not find any additional information on the screenshot, and will as the same question: What is the purpose of the screenshot? This is kind of good style: To provide as many details as needed, but to keep the question as short as possible.
    – mentallurg
    Jan 2 at 6:59
  • 6
    I kinda like the screenshot - it demonstrates the issue in a more obvious visual way.
    – Simon East
    Jan 2 at 11:32
  • 1
    The browser isn't necessarily more trustworthy for the user than the server – in particular if the JavaScript performing the client-side crypto would be loaded from the same server where the plaintext password would be sent. If the server is compromised it could serve different JS to exfiltrate the password.
    – amon
    Jan 3 at 0:09
15

I don't see to how your proposal is better than existing client side hashing approaches, but I find it as more complex to implement than others. Unfortunately you don't describe a specific risk you are trying to access so I just assume the typical threats commonly seen.

Man in the Middle attacker

In this case it is assumed that some man in the middle has access to the traffic, for example because it compromised some trusted traffic TLS interception in a corporate firewall or got hold on a trusted CA as in case of superfish.

In this scenario the attacker gets access to H the same as they did before with plain_password. Since H is everything what is needed for authentication the attacker is thus successful and your approach does not add any additional protection here.

Hiding weak passwords and password reuse

A common argument for client side hashing is to not expose a weak or reused password to the server, but instead authenticate with a complex derived password. Your approach does this with hashing the plain_password with some user generated random salt and then send H and salt to the server on password setup.

While this works every authentication now requires an additional step: first it needs to retrieve the previously used salt for the user from the user and then it can use this salt to hash the plain_password. This additional step makes authentication more complex since first it needs to check the user with the server and later it can check the password. Additionally a trivial implementation of this opens an information leak since it makes it possible to check if the user exists in the first place (salt returned or not) without further authentication.

This information leak can be closed by the server returning some salt no matter if the user exists or not. Of course this cannot be just a random salt since otherwise an attacker could just check twice the same user and conclude that the user does not exist if the returned salt was different. So the salt actually has to be fixed for the non-existing user, i.e. derived from the user name.

And this also shows a path to simplify your approach: instead of generating a random salt by the user, storing it at the server and retrieving it later again, one could simply derive the salt from the user name at the client side. A simple salt=hash(username+domain) would be sufficient to generate a salt which is unique per domain and thus make both salt and H different even if username and plain_password get reused on different domains. And contrary to your approach no additional trip to the server is needed to first retrieve the previously used salt for the user.


In short: this simplified approach is basically sending hash(plain_password+username+domain) instead of the original password. The domain is added to make sure that even if username and plain_password are reused over multiple sites, the derived password is not reused.

9
  • Thank you for your analysis, that I'm starting to read! Just a short note you don't describe a specific risk you are trying to access: I should add this to the question: the main goal is that the server never ever has access to the plaintext password or a simple hash of it (that could be often reversed with a single rainbow table for the whole site). I'm ok for the risk that an attacker has to compute one rainbow table per user.
    – Basj
    Jan 2 at 7:42
  • @Basj: "server has access to plaintext password" is not a risk itself but just the description of a technical fact. The actual risk is what bad things might happen if the server has access to the plaintext, i.e. why one want to avoid this. Jan 2 at 7:44
  • Server has access to plaintext password + compromised server (example Spectre / Meltdown vuln) => plaintext password could be stolen, before it is salted-hashed + saved to DB.
    – Basj
    Jan 2 at 7:46
  • 1
    @Basj: In this case H could be stolen too, i.e. this is comparable to the MITM problem and client side hashing will not actually help. Jan 2 at 7:48
  • 1
    @Basj: Yes, and this is the actual risk here - possibility of reusing a server-side compromised password on another site. Jan 2 at 7:59
9

This is exactly the problem that protocols like PAKE and SRP aim to solve. With PAKE/SRP, the client and the server mutually authenticate each other based on a password known to the client (and a derivation of the password known to the server).

The client demonstrates to the server that it knows the password, without the client sending the password (or password-equivalent data) to the server. At the end of the process, the client and the server share a shared secret.

The server does not store the password (or password-equivalent data) and is not susceptible to dictionary attacks. An eavesdropper or man-in-the-middle able to view the plaintext sent over the wire is not able to gain enough information to derive the password. This effectively prevents man-in-the-middle attacks using fake certificates, and prevents 'phishing' sites from stealing users' passwords.

For a good write-up of how 1password implemented SRP, see https://blog.1password.com/developers-how-we-use-srp-and-you-can-too/

4
  • I think this answer needs to mention the number of transferred messages,
    – kelalaka
    Jan 2 at 19:05
  • 1
    @kelalaka just two round trips between the client and the server. See en.wikipedia.org/wiki/Secure_Remote_Password_protocol
    – mti2935
    Jan 2 at 22:47
  • @mti2935: As far as I understand SRP needs to query the client-specific salt from the server - which allows exactly the information leak I've described, i.e. detect if a user exists. Jan 3 at 9:23
  • @SteffenUllrich fair point. I went back and read the spec for SRP, and I agree with you that SRP (as described in the spec) would leak whether or not a user exists. But, it seems that the protocol could be modified to serve a bogus salt and verifier in response to the first request from the client if the user does not exist. Then, the authentication would fail in the same way (and with the same server response) regardless of whether an incorrect password was provided for an existing user, or if the user does not exist.
    – mti2935
    Sep 2 at 11:52
5

In addition to the answer of Steffen Ullrich:

If during login the user sends the hash only, then the attacker does not need to know the password. It is sufficient to steal the password database. Then during login request the attacker will just send the hash from database. The server will not distinguish if client used the password and hashed it, or if the client (attacker) simply sent the hash.

The article about OPAQUE addresses also this problem: Stealing the password database will not help the attacker. One would need to know the plain user password.

4

If the attacker compromised your server, they are in control not only of the software running on your server but also of the software running on the clients.
No matter what beautifully engineered authentication scheme you designed, the attacker can alter it before it's sent to the browser.
You now have an egg-chicken problem: you can't secure a password if the attacker controls the way it's collected and send to your server.

If you are worried about a data breach, your method would work as a protection, but so would a proper password hashing server side.

If you are worried about MITM attacks, TLS solves them.
If you are worried about MITM attacks over TLS, then, as I like to say, a good defense against them always starts with a Krav Maga manual. An attacker that has enough resources to break TLS consistently has no problem getting what they want from any non properly and specially trained individual (yes, I'm talking about torture, blackmailing, kidnapping and murdering).

If you are worried about a threat actor that can only read the data received by the server, then your approach (as corrected by Steffen) will work against them. However, this is a weird and rare circumstance, often arising from a grossly misconfigured server and bad developing practices (i.e. sending credentials over GET requests and storing the access log publicly). It's easier to fix these mistakes than to invent a protocol just to deal with them.

Note that both of the vulnerabilities you mentioned (actually it's just one, as Meltdown is technically a variant of Spectre) would eventually result in a local privilege escalation, giving the attacker full control of your web server. Again highlighting how rare is the scenario where an attacker has read-only access to the data received by your web server.

So, the reason many big sites don't use it, it's because it adds pretty much nothing but in specific circumstances that are most likely misconfigurations. It's also worth noting that if an attacker can read what data is transitioning at your server, you are far into the losing side of the game. Mind me, it's good to have layered protections, but your main goal is not to have it happen in the first place. And focusing on that would also spare you from inventing new schemes.

Anyway, as Steffen showed, your proposed scheme could work again such a weird attack model. I would still use hash(hash(domain + username) + password) rather than hash(domain + username + password) just to rule the remote possibility that domain + username + password is still a word in a dictionary.
As mti2935 showed, SRP is a more interesting alternative. Certificate-based authentication (i.e. the one handled by the browser) is another option (which I find better than doing it manually in a, potentially tainted, JS script, as you seem to have proposed in the comments).

1
  • Thank you for your answer, this helps to see the big picture.
    – Basj
    Jan 3 at 13:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.