0
$\begingroup$

I'm prototyping an app that uses client-side password hashing in order to hide the "real" account password from a server (the server hashes any recieved password anyway, so no plaintext records in the database). It seem to be a controversial thing to do but I didn't find any convincing arguments against it (I'm new to cryptography so please correct me if I'm wrong).

I'm trying to figure out what would be an appropriate salt for Argon2i. Many implementations generate random salt by default but it won't allow users to log in again because using new salt produces new hash. That's why I can't avoid using deterministic salt.

My understanding is: each salt value is better to be globally unique for each user so it seems like an email address could make an OK salt in a sense that it's unique, but not globally. Email + password would certainly lower the chances of a collision but some users might have the same passwords in many apps. I'm thinking about sha256(app name + email). Would it make a good salt?

My main question is about the implications of using a deterministic salt in general. Does it make the resulting hashes easier to crack? And if it's OK, what would be an appropriate way to generate such a salt?

Another related thing I'm wondering about is how easy is it to crack the hash and not the password itself? Let's say I use Argon2 to hash a password and use the same exact password as hash (argon2(pwd = "foobar", salt = sha256("foobar")). Would it help an attacker to reveal that password by any method except using a rainbow table?

$\endgroup$
4
  • 1
    $\begingroup$ You want to store the salt with the hash so then you can reproduce it. Generate a new random salt every time you hash a password, even for the same user. If you use a deterministic salt then it makes it easier for an attacker as he can then generate a table of common password hashes no matter how often a user changes his password. $\endgroup$ Feb 11, 2020 at 18:30
  • $\begingroup$ @Swashbuckler in my scenario, password-derived hash is used as a back end account password so using a random salt would block the user from logging in again (server would simply think that someone tries to log in with a wrong password). I think I've got your point on generating a table based on guessable salts, would using the password as a part of salt material solve this problem? Something like salt = sha256(app name + email + password). $\endgroup$ Feb 11, 2020 at 18:44
  • $\begingroup$ Clearly sha256 can easily be cracked, since it is made by the NSA just like sha1, which was cracked just recently. $\endgroup$
    – super
    Feb 11, 2020 at 19:42
  • $\begingroup$ One of the advantages of using a salt is that two passwords don't hash to the same value, so you can't tell from a database dump which users share the same password. If you use the password (or any deterministic transform of it) as the salt then you lose this advantage. $\endgroup$ Feb 11, 2020 at 20:48

2 Answers 2

1
$\begingroup$

It seem to be a controversial thing to do but I didn't find any convincing arguments against it

It can be a good solution if you can transmit the salt from the server to the client, e.g. in apps or desktop apps and has some nice load-distribution properties.

the server hashes any recieved password anyway, so no plaintext records in the database

This is actually important so a leaked database doesn't immediately allow for logins.

Would it make a good salt?

It would work. However the "issue" is that it allows for targeted pre-computation attacks. So suppose you have an adversary who already knows a set of target users. For them they can now pre-compute a lot of password hashes so once the password database is compromised there's a very low latency to password recovery for the targets if the precomputed hashes contain a match. If you're not worried about such attacks uniqueness is the only property required from salts and may be achieved in any arbitrary way.

Indeed if you have control over the client and have bidirectional communication, using a protocol like OPAQUE is preferred as it also hides the salt.

$\endgroup$
5
  • 1
    $\begingroup$ So, the thing that enables a pre-computation attack is the fact that an attacker can reverse-engineer a client app, extract the deterministic algorithm and use it to find out the salt for every user. After that, the attacker can run Argon2(password, salt) on a huge password set to build a dictionary (password --> server_hash), there is no need to break into the server yet. Then, the attacker have to obtain a copy of a server DB and crack the server hashes first in order to be able to compare them with the pre-computed dictionary hoping to find a match (password)? $\endgroup$ Feb 12, 2020 at 8:06
  • $\begingroup$ @IgorBubelov yeah, you can build the list probably to match the server-format / do the (cheap) server-side hashing once the DB is leaked. $\endgroup$
    – SEJPM
    Feb 12, 2020 at 8:45
  • 1
    $\begingroup$ Does it mean that the fact that the salt is independent from the password makes things worse in such a scenario? The user may change the password but it wouldn't change the salt (sha256(app name + email)) so the same pre-computed dictionary would still be valid. So, it looks like sha256(app name + email + password) might produce a better salt because it changes if user change the password. $\endgroup$ Feb 12, 2020 at 10:13
  • $\begingroup$ @IgorBubelov Yeah, you are correct with this analysis. $\endgroup$
    – SEJPM
    Feb 12, 2020 at 11:01
  • 2
    $\begingroup$ I guess that answers my main question. My understanding is that deterministic client-generated salt enables pre-computation attacks. Changing salt each time user changes the password might help a bit but it doesn't solve the main issue. Strong enough password and/or slow enough Argon2 settings on the client are effective means to make it less of an issue. $\endgroup$ Feb 12, 2020 at 11:28
1
$\begingroup$

I'm prototyping an app that uses client-side password hashing in order to hide the "real" account password from a server (the server hashes any recieved password anyway, so no plaintext records in the database). It seem to be a controversial thing to do but I didn't find any convincing arguments against it (I'm new to cryptography so please correct me if I'm wrong).

I don't think it's controversial so much as not much explored. There's a couple techniques that have been proposed for shifting the costly password hash over to the clients, for example:

These both work in a similar fashion. For example, to enroll a new password:

  1. The server picks a random client_salt and sends it to the client
  2. The client computes client_hash = pwhash(client_salt, password) and sends it to the server
  3. The server computes server_hash = fasthash(client_hash), and stores (username, client_salt, server_hash) in its password database

To log in:

  1. Client sends username to server
  2. Server looks it up in the password database and sends the corresponding client_salt to client
  3. Client computes client_hash = pwhash(client_salt, password) and sends it to server
  4. Server computes server_hash = fasthash(client_hash) and checks whether it matches its password entry

Downside compared to server-side hashing:

  • Requires a roundtrip between client and server.
  • What do you do if a client requests a username that doesn't exist? A naïve implementation allows an attacker to probe whether a username exists, which might be undesirable in some systems. One mitigation is to respond to requests for nonexistent users with a fake client_salt (which should probably be a pseudorandom function of the username), but even that might be defeated by timing attacks.
  • The server discloses the client_salt value to any attacker who probes for that user ID. Which means that an attacker can start precomputing hashes for users of interest before they manage to steal the server_hash stored on the server.

My understanding is: each salt value is better to be globally unique for each user so it seems like an email address could make an OK salt in a sense that it's unique, but not globally. Email + password would certainly lower the chances of a collision but some users might have the same passwords in many apps. I'm thinking about sha256(app name + email). Would it make a good salt?

The problem with these is that the salt ideally should be unique not just for each user as you presuppose, but rather for each password database entry state for the same user. What this means is that if a user changes their password, ideally a new salt should be generated for the new password entry, so that work cannot be reused for attacking both the old and the new password entry. None of your proposals achieves that.

Another problem is they allow an attacker who obtains or guesses email addresses and/or the app name string to precompute client-side hashes for users of interest before they manage to steal the server-side hashes. But note this problem is shared with the scheme sketched above—and more generally, it seems this cannot be solved unless you have a stateful client that generates and locally stores its own random client_salt value that it keeps secret from attackers. This would seem to defeat the purpose of passwords (because you're then relying on the secrecy of a proper cryptographic key), but systems that do something like this exist—for example, 1Password has a "Secret Key" concept where their client generates a cryptographic key that they instruct you to print out and securely store, so that an attacker cannot crack the server-side passwords without stealing your device or the printed copy of the Secret Key.

Another related thing I'm wondering about is how easy is it to crack the hash and not the password itself? Let's say I use Argon2 to hash a password and use the same exact password as hash (argon2(pwd = "foobar", salt = sha256("foobar")). Would it help an attacker to reveal that password by any method except using a rainbow table?

The problem here is that in addition to being unique for each password entry state, the salt should ideally be independent of the password. This final example, where password = salt, is trivial to attack just by making a table of its result for very common passwords.

$\endgroup$
1
  • $\begingroup$ Ah, I think I get it now. So, sha256(app name + email) allows to keep building a dictionary no matter how many times the user changes a password. Would it be better to use sha256(app name + email + password) as a salt? That would solve the problem with password database entry state not being unique, wouldn't it? It kind of breaks the second "best practice" that you mentioned though, the one about independence of salts and passwords. That would make the salt dependent on 3 variables and password is just one of them, would it still be a bad idea? $\endgroup$ Feb 12, 2020 at 7:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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