3
votes

I'm building a REST API for a mobile app. I need to handle authentication. As far as I could find, these are the best practices regarding password storing.

On the client side-register

  1. Let user choose a password
  2. Create a sufficiently long random salt from a strong random number generator
  3. Append the the salt to the password and hash it.
  4. Send the hash and salt to the server and store it in database

Server side-log in

  1. Expose a method that returns user's salt for a given userid(email)

Client side-log in

  1. User types in his userid and password
  2. Retrieve the salt for the given userid
  3. Append the the salt to the password and hash it.
  4. Send the hash to the server, check that it's the same and authenticate user

Obviously the week point in this approach is that an attacker can see the salt for any user effortlessly. Is this ok? Obviously I can store the salt on the client side as well but if e.g. the user is logging in from a different phone he has to be able to retrieve the salt.

The alternative to this is to send the password in plaintext and handle all the salting and hashing on the server side. I would use HTTPS.

Which of these approaches is preferable? Is there a third one I'm missing?

2
never do anything clientside. the client should send the password to the server, and the server does the hashing/verification. "oh, you lost a $5 bill last night? Here's our entire cash intake from the past week. Feel free to take it home and look through it for your $5.. but be honest and don't take anything else nudge nudge wink wink" - Marc B
The weak point is that the client cannot prove without a doubt to the server that the authentication actually happened. - Artjom B.
@ArtjomB. could you expand on that? I don't follow.. - Juraj Petrik

2 Answers

3
votes

Obviously the week point in this approach is that an attacker can see the salt for any user effortlessly.

No, the weak point is that your server is trusting the client. Here is MSDN's brief and pointed take on the situation:

You should never trust user input directly, especially if the user input is anonymous. Remember the two golden rules: never trust user input, and always check data as it moves from an untrusted to a trusted domain.

Source: Do Not Trust User Input Directly

Here you have user data moving from their untrusted client to your server. You need to verify their input on your server. There is no attack that you can prevent by performing the generation of the salt and the hashing of the password on the client. The ONLY way to secure the server-client communication is with secure handshake key exchange for asymmetric encryption. This occurs over HTTPS, and once the link is secure, then sending the password in plaintext is no less dangerous than sending the code to the client to perform the hashing client side.

If you do not secure the connection to the client with HTTPS, you are always vulnerable to a Man-in-the-Middle attack. Even though the password is nominally obscured when returning to your server, an attacker could instead impersonate your server to the client, sending an HTML form that skips the hash or sends the password in plaintext to their server as well.

Exposing the salt in your scenario is an additional security hole, but it is minor next to the glaring hole that would allow an attacker to get the password directly. I can explain this attack further if you desire, but it is a totally pointless attack unless the password itself is better secured; and once that happens, you have no need to expose the salt in the first place.


Responding to comments, it is true that salts can be made publicly known without reducing security if you are certain that they were generated correctly. For a salt, that means they are of sufficient length, sufficiently random, and in some schemes that they are cryptographically secure. The problem is that you allow the client to generate the salt, which means, I think we all agree now, that they can't be trusted. Maybe an attacker corrupted an external library you're relying on for PRNG. Maybe some user devices have an insufficient entropy pool. Maybe their device isn't secure against side-channel attacks. If you're making an Android App, there are certainly some very insecure devices out there. In Apple's walled garden, these risks might be tolerably low, but you can never eliminate them.

Like I said originally, however, these risks are minuscule next to the real dangers of your scheme. Namely, you're transmitting the hashed passed in the clear to your server. Even if you hash it further before storing it in your database, so a SQL injection can't just dump all the user passwords, you're still vulnerable to Man-in-the-Middle attacks. Unencrypted HTTP traffic can be observed and recorded by any router it passes through. Once an attacker observes a hashed password, they can re-send the same request to your server, and be authenticated incorrectly.

3
votes

You should strictly go for the alternative, "to send the password in plaintext and handle all the salting and hashing on the server side. I would use HTTPS."

Of course the sentence above is highly contradictory: if you would be using HTTPS then the password would not be in plaintext. You should use HTTPS.

At the server you can store the randomly generated salt as plaintext in the database. You should not opt for a normal hash but a password hash - such as one generated by PBKDF2 - instead.

The salt is there if your DB gets stolen: the salt will make sure that identical passwords are not revealed to be identical. Furthermore the salt will make it impossible to use rainbow tables with precomputed password hashes.

Two important things to notice: for web clients you will have to rely on the trusted TLS certificate store for creating a secure TLS connection. Furthermore, your client may not have access to a good random number generator.