Score:5

Sending password to server vs. sending SHA

vu flag
Per

This is an existing website with approx. 100K accounts, and passwords are hashed using bcrypt with a high number of rounds.

The current design that I'm questioning is that we're sending the username and password to the server, and doing the bcrypt on the server, instead of sending a SHA generated in the browser and bcrypting that, so that we never touch the user's password.

The original rationale was that if we're sending a SHA, then the SHA just becomes the password, and nothing is gained. But that doesn't seem true.

We see pretty routinely that people try to log in to our website using their Apple or Google username/password combo, because they don't fully understand the difference between our systems and Apple/Google's systems.

Now, their clear-text password does hit a highly isolated system behind the AWS load balancer, and this is even more isolated in the future. We touch the clear-text password for a microsecond and then forget.

But it still makes me queasy. So the question is, what's the remedy?

  • Would sending a SHA to the server be better, so that we at least never see the password server-side, even briefly?

  • What would a migration path look like, beyond changing this and resetting all passwords, requiring users to create new passwords whose bcrypt is now based on a SHA?

  • Any other ideas?

samuel-lucas6 avatar
bs flag
Seeing a hash of the password is an improvement on seeing the plaintext password, although it doesn't help with weak passwords. There are issues with pre-hashing and bcrypt though, which led to [hmac-bcrypt](https://github.com/epixoip/hmac-bcrypt). A much simpler, albeit slightly less secure alternative to OPAQUE is like how passkeys work, which involves a challenge-response protocol. It's explained [here](https://00f.net/2018/10/18/on-user-authentication/), and I'd recommend reading [these](https://www.slideshare.net/jedisct1/improving-passwordbased-authentication) slides as well.
kr flag
@samuel-lucas6: Convert your comment to an answer and will vote it up.
Score:2
bs flag

This is an excellent question, and one of the motivations for OPAQUE, as explained in the Introduction section. Unfortunately, PAKEs are still rarely used as far as I know, so this issue has sort of just been accepted.

Would sending a SHA to the server be better, so that we at least never see the password server-side, even briefly?

Yes, it's a slight improvement from that perspective. However, hashes for weak passwords will be well known and easy to find, so it doesn't solve the issue completely if such hashes are logged.

What would a migration path look like, beyond changing this and resetting all passwords, requiring users to create new passwords whose bcrypt is now based on a SHA?

The other dilemma is that pre-hashing is particularly problematic with bcrypt and thus often not recommended.

  • Unsalted/unpeppered hashes can allow shucking attacks. This is an argument against pre-hashing beyond just bcrypt.
  • Null bytes from pre-hashing can lead to colliding passwords.
  • Some implementations reportedly can't handle binary inputs properly.

Solutions include Base64 or hex encoding the hash to address points 2 and 3, not pre-hashing, or using hmac-bcrypt, which is designed to address all of these problems whilst performing pre-hashing. You could replicate the hmac-bcrypt pre-hashing approach.

Any other ideas?

You could use public-key cryptography without needing to rely on a PAKE. This is easier to implement but less secure because it doesn't prevent precomputation. The idea is explained nicely in a blog post by Frank Denis, author of the libsodium and LibHydrogen cryptographic libraries.

In simplified terms (please see the blog post for the full details), it goes like this:

  1. Do client-side password-based key derivation (not bcrypt, which isn't a KDF) to generate a deterministic seed. Use context string || username as the salt, or you could get the server to send a salt after the user provides their username.
  2. Generate a key pair on the client from the seed.
  3. The server sends a random 256-bit nonce to the client.
  4. The client computes a signature over context string || username || nonce and sends it to the server alongside their public key and username.
  5. The server verifies the signature using the received information.

Now passwords don't need to be sent to the server, and there's no server-side password hashing DoS risk. However, passkeys are hopefully the future.

Per avatar
vu flag
Per
Beautiful answer that teases out the exact issues I was looking for. Top notch!
Per avatar
vu flag
Per
I had no idea about shucking, and it says it right there in the docs, and also that hmac would be the correct digest if one wished to pre-hash.
Per avatar
vu flag
Per
For right now, I'll leave it alone. I'm not in favor of OPAQUE or PAKEs, because it adds a huge, complex component, setting off my alarms. Thanks for laying out the landscape.
samuel-lucas6 avatar
bs flag
Thanks. That's fair enough. Maybe keep an eye on [passkeys](https://www.passkeys.io/) though, which are just starting to roll out.
kr flag
The solution based on asymmetric encryption that you described is definitely more secure compared to other methods mentioned in this thread. But there are some problems in the answer.
kr flag
1) *"pre-hashing is dangerous with bcrypt"* - This sounds way too generic and in such form is not correct. A) There is a risk only in case the same hashing function *without salt* is used. B) In such case there is an issue with *any* function that use pre-hashed password instead of the plain password: scrypt, PBKDF2, Argon2, etc.
kr flag
2) Shucking is applicable only if many specific conditions hold true: A) The same passord is used on multiple web sites (65% users do that, according to Google). B) Hashes of one of these web sites were leaked. C) The same hash function was used, means if the author wants to use SHA-512, the leaked web site should also use SHA-512. C) No salt was used during pre-hashing, as you correctly mentioned. D) The user has not changed the password over a long time.
kr flag
3) *"Null bytes from pre-hashing lead to colliding passwords"* - This happens under specific conditions only. A) Implementation has a bug expecting that strings are limited by 0x00. If strings are considered as sequences of given length independent on their content, like String in Java, this case is *not relevant*. B) Client application sends hash in the binary form. If the hash is sent in hexadecimal form, the issue is *not relevant*.
kr flag
4) *"Some implementations can't handle binary inputs properly"* - This holds to any approach *including yours*, because public key is binary. Thus **you contradict to yourself**. I like the solution you suggested, but I'd suggest you to remove this statement.
kr flag
If you improve your answer, I'll vote it up.
samuel-lucas6 avatar
bs flag
This is why I often stick to comments; it takes a lot of time I don't necessarily have to write a good answer. 1) Pre-hashing is not actively discouraged for other algorithms like it is for bcrypt as other algorithms don't suffer all of the same problems. Some protocols pre-hash. I agree it could be phrased better though. 2) That's explained via the links and hence the word 'can', not 'will'. 3) I originally discussed Base64/hex encoding the hash but removed it for simplicity due to hmac-bcrypt. 4) I didn't make it clear, but bcrypt wouldn't be used for this use case as it's not a KDF.
kr flag
@samuel-lucas6: You write about problems of handling of binary inputs. The same is true **for your** approach. When the client sends public key to be stored on the server, or when the client sends signed nonce to the server, these are **binary** data. Means, also the approach you suggested can have problems with handling of binary data.
samuel-lucas6 avatar
bs flag
@mentallurg That's an issue for bcrypt, which I've since clarified isn't used to derive the seed. The way you encode/send the data is beyond the scope of an answer.
kr flag
Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/141599/discussion-between-mentallurg-and-samuel-lucas6).
Score:1
et flag

Would sending a SHA to the server be better

No.

If the hashing is done server side & the server's password database leaks, it's useless because a client cannot use the hash for logging in because client has to send the password & not the hash for logging in.

If the hashing is done on the client side & the server's password database leaks, then a fake client can login by sending the hashes to the server.

So that way is less secure.

Any other ideas?

PAKE - Password Authenticated Key Exchange - https://eprint.iacr.org/2018/163.pdf

Per avatar
vu flag
Per
You may be misreading the question slightly. It's not either/or. The choice is between sending the clear-text password AND bcrypting on the server, versus sending a client-side SHA, and STILL bcrypting on the server. The difference then seems mainly that we lose the ability to see the password server-side, which is desired.
et flag
@per The client side SHA is then like the password, right? So the advantage you get is only if this kind of protocol doesn't become popular & you are the only one using it. The moment this protocol becomes popular, then the SHA hash is again the password for a majority of the systems & the original problem you are trying to solve resurfaces.
Per avatar
vu flag
Per
Not really. I means that only the browser sees the actual password, so at least we don't have the responsibility of having Apple/Google logins thrown at us, because what passes through our servers can't be used to log in to Apple/Google. But certainly, it's only half of it. Our website could get hacked and some Javascript could exfiltrate passwords before the SHA. The point is to reduce the touch points, in a PCI spirit. Nothing beats not touching things.
et flag
@per - `But certainly, it's only half of it` - Assuming both you & Apple/Google use your suggested protocol, then what else is the unknown half? What am I missing?
Per avatar
vu flag
Per
OK, let's say our SHA is a concatenation of "ilovemeatballs" + the user's password. That will never be Apple's protocol. The point is only to stop the clear-text password from hitting our servers.
et flag
@per - so you are concatenating a server specific string "ilovemeatballs" to the password before hashing on the client side & you will be using the same string "ilovemeatballs" for all users of your system? You should consider adding this detail to your question.
Per avatar
vu flag
Per
Again, it doesn't matter. I don't know how to keep this on topic. The question is hashing, BY ANY MEANS, which is why I'm not even specific about the algorithm. I feel like this is side-tracking into irrelevant personal favorites over the real problem that we're being sent Apple/Google logins every day, and we don't want to see them. We can prevent seeing them by pre-hashing before the API call with a constant salt. The goal is to avoid people's Apple/Google credentials from leaving the browser. That's what I want ideas for. Pre-hashing is one way.
et flag
@per - it does matter whether you keep a common prefix for all userpasswords for your server before hashing - that's what prevents your protocol from degrading into "hash as a password". If you don't have a server specific prefix, then the SHA is a password & when you see the SHA, you are seeing the google/apple password because the SHA is their password also.
Per avatar
vu flag
Per
I think this conversation is derailing. Thank you for your time.
Score:1
kr flag

TLDR

If you want to choose between two methods only, sending plain password or sending password hash, then sending hash is better.

Details

If there is a mistake on the server side and the password was written to a log, this may give some insights about how particular user generates passwords: What characters are used, how long the passwords are, how much are the passwords indistinguishable from a random sequence.

Besides, as you mentioned, some users may reuse their password for multiple systems. If mistakenly written to the log, this may be a security problem.

If users send hashes, you will avoid these potential problems.

About brute-forcing: SHA is very fast by design. That's why trying some character sequence as a password directly, i.e. applying bcrypt directly, or first hashing with SHA and then applying bcrypt, will take similar amount of resources. In both cases the success of brute-forcing will primarily depend on the cost factor that you use in bcrypt.

But if you want that the client doesn't send even the hash, consider what the others suggested, PAKE / OPAQUE.

To avoid the risk related to password reuse and thus password shucking, you can add a salt during pre-hashing, e.g. user you application name or domain name as a salt. Then for the same password with the same hashing function you will get a different hash value.

Maarten Bodewes avatar
in flag
Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackexchange.com/rooms/141578/discussion-on-answer-by-mentallurg-sending-password-to-server-vs-sending-sha).
Score:-3
in flag

If you are designing from scratch then try this -

Server-Side/Back-End :

Store the passwords using password-based-encryption or HMAC-HASH (derive a key from the password + use a salt that is known publicly but specific to your organization).

This way even if the user has re-used passwords and the database is compromised (last-pass is the most recent one as of writing), the hacker cannot mount a dictionary attack.

Client-Side/Front-End

The reason for the salt being public is simply because that's the only way we can repeat the process at the client end.

Hence, once the user enters the password, repeat the process (the client has the public salt and the password hashing/key-derivation algorithm).

This way the clear password never reaches the server (which I guess is what you are looking for).

Rollout-Strategy

This would be a prolonged one. You can auto-enroll all users at the point of successful login to the new system. But you'll need to retain old user's on the older setup/format. Also you'll need to handle both old and new format's at the client and server end (have some kind of a flag to determine whether the user is on the older setup or the new one). If two-steps are fine, you could first ask the user for the email and then figure out which system they are on (old or new) and subsequently prompt the user for the password accordingly !

Update 29-Dec-2022 :

To address the concern mentioned in the comments, where the database itself is compromised, we can introduce the following modifications to the scheme -

  1. Include the User-Id as part of the the public-salt. This way each password will effectively have its own salt ! So what is sent to the server is effectively - hash(hash({#clear-password}), {#user-id}, {#server-public-salt})

    Let's call this resulting value as client-user-credentials.

  2. The server while storing, will now apply a private hash on the above user-secret and then store. So the server effectively stores - hash({#client-user-credentials}, {#server-private-salt})

  3. Going forward, the client always calculates client-user-credentials and transmits to server. This does not remove the possibility of man-in-the-middle scenarios but does address the concern that even in the event of a database breach, it would not be possible for the hacker to mount an attack that would compromise the account.

There is still however a possibility that if at all the server is compromised, the hacker can linger for some time and capture the clear client-user-credentials but that would be difficult to solve and perhaps other schemes suggested else-where in the answers can be used.

kr flag
If an attacker obtains database with hashes, it will be sufficient to send hashes, and the server will accept them. This will be a serious security problem.
in flag
Thanks. Had not considered that. Perhaps we can introduce a re-hashing step at the server.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.