27

This question already has an answer here:

In usual network applications, that employ password hashing, is the user password hashed on client side before sending it to the server, or is it sent without hashing as encryption of plain text password to the server?

The main question that I have is if an attacker discovers the hashed passwords then why can't they just send hash to the server to authenticate in case of client side hashing?

marked as duplicate by D.W., Neil Smithline, Steffen Ullrich, Tobi Nary, LvB Apr 15 '16 at 7:44

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

39

At least part of the hashing must occur server-side. Indeed, whatever the client sends to the server grants access, so if that very same value is just stored as-is on the server, then you have the same issues as plaintext password storage, namely that a single read-only glimpse at your server database gives all the accesses.

In most cases all the hashing is done on the server, because the client is a Web browser than can, at best, do some Javascript, and Javascript is not very good at cryptographic tasks. The client sends the password itself (through HTTPS of course), and the server computes the hash, and compares it with the stored hash value. The attacker cannot simply "pass the hash"; he must send a value that, when hashed by the server, yields the stored hash value.

If the clients are good at making crypto (e.g. a heavy native client for a game), then the bulk of the hashing process can be done on the client side, but there still needs to be a final hash computation done on the server.

  • 4
    Javascript is not very good at cryptographic tasks You mean performance would be a problem? but how intensive could be hashing a 8-20 character password that it would interfere with client experience? – Allahjane Apr 13 '16 at 16:46
  • 14
    The theory of password hashing is that it MUST be computationally expensive; see this answer. Using a low-performance language implies a less expensive hashing, which makes dictionary attacks easier. Also, beyond performance issues, Javascript uses a computing model that is known to make things hard for crypto implementations (e.g. no true integer type). – Thomas Pornin Apr 13 '16 at 16:56
  • 1
    Letting the client hash the password stops you from using salt and opens you to replay attacks (whats the point of hashing if you aren't going to trust it?) unless you implement a complex CHAP. I think denouncing hashing in JavaScript is done because of the weaknesses of JavaScript protections during that, not that it is in any way lacking in performance (which varies wildly, on the browser implementation and CPU). Here is an article that covers most of the bases: nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/… – Jeff Meden Apr 13 '16 at 20:41
  • 1
    @JeffMeden That is correct which is why the server must do most of the hashing. However pre-hashing a little client side can help prevent sniffing from getting the plain text password which, given password reuse, is a good thing. – AstroDan Apr 13 '16 at 20:47
  • 2
    @Allahjane It is not just because of performance that javascript is a poor choice for cryptography. It is also because an attacker who can control the server or break TLS can send arbitrary javascript code to the client anyway. So in almost every scenario it is impossible to improve security by doing cryptography in javascript. – kasperd Apr 13 '16 at 21:21
24

How login password hashing is supposed to work

Note that in all snippets, I am intentionally excluding security features because I want to keep it compact. Do not rely on these code snippets for anything.

In usual network applications ,that employ password hashing, is the user password hashed on client side before sending it to the server or is it sent without hashing as encryption of plain text password to the server?

No, it's hashed server side. You send the password over plain text, or through HTTPS, to the server. The server will then generate a hash for the password, like so:

public void RegisterAccount(string username, string password, string email)
{
     if (!Database.EmailExists(email) && !Database.UsernameExists(username))
     {
         Database.AddNewUser(username, Hash.GenerateHash(password), email);
     }
}

Assume that Database.AddNewUser(string, string, string); will insert the username, hashed password, and the email into the database. The hash is generated based on the password.

Let's say you put in "don't hack me please" as the password. bcrypt will turn that into $2a$08$BOHyNdXAVkWOUgPG/hvsjew7ZpngqJIgueY6m76xd4y3UllXUZLBy, or some other variant based on the salt. The database now looks like this:

+--------------+--------------------------------------------------------------+--------------------+
|    user      |    password                                                  |       email        |
+--------------+--------------------------------------------------------------+--------------------+
| markbuffalo  | $2a$08$BOHyNdXAVkWOUgPG/hvsjew7ZpngqJIgueY6m76xd4y3UllXUZLBy |   mark@buffalo.gov |
+--------------+--------------------------------------------------------------+--------------------+

Normally you're going to call the database to read the password for that username. Once you call the database, you're going to use a function, such as Hash.ValidateHash(), to see if it matches. If it matches, you can log in using that non-hashed password.


Why can't you just pass the hash to log in?

You can in some very unfortunate cases.

The main question I have is if an attacker discovers the hashed passwords then why can't he just send hash to the server to authenticate in case of client side hashing?

This is possible, but it can only happen during severe instances of massively epic failure on behalf of the developers. In fact, I've seen this before, and I'll show you how this can happen via code:

public void Login(string username, string password)
{
    if (Database.LoginMatches(username, password) ||
        Database.LoginMatches(username, Hash.ValidateHash(password)))
    {
        logMeIn();
    }
}

In the above snippet, the login wants to check to see if either the user has the hash, or the password which will validate into the hash stored in the database. This is not the correct way to do it. In fact, it's one of the worst instances of epic fail I've ever seen in my life.

With this code, you can simply enter the hash, or the password, and log in. Recall the previous table. Either of the following logins will work correctly:

  1. Login("markbuffalo", "$2a$08$BOHyNdXAVkWOUgPG/hvsjew7ZpngqJIgueY6m76xd4y3UllXUZLBy");
  2. Login("markbuffalo", @"don't hack me please");

That is the wrong approach entirely. It's not supposed to happen. At. All. Nevertheless, some developers have taken this approach for reasons I do not know.


What is the right approach to hash validation?

Like Rápli András said, h(x) does not match h(h(x)). The right approach is to check the incoming password against the hash to see if they match. If they do not match, do not use any OR magic (||); simply do not allow them to log in.

  • 1
    A likely reason for code like what you showed above is as a migration strategy. If an application used to not hash passwords, and then adds support for hashing, the passwords stored in the DB may not all be hashed yet, so both may need to be supported. But I would expect that to be temporary. You should not need to let that code live forever. The goal should be to get all passwords hashed ASAP and then remove that code. – Brandon Apr 14 '16 at 15:10
  • @Brandon That kind of migration strategy would give me tuberculosis. :-P This is a very easy fix, to properly hash the passwords. If you're using a different hash algorithm, you can either require users to reset their passwords, or you can use something like Hash.ValidateOldHash(password); while migrating. If the passwords are in plaintext, then, again, you can require users to reset their passwords, or you can convert the plaintext to the appropriate hash and automatically update the user list records for all users. – Mark Buffalo Apr 14 '16 at 15:46
  • 1
    I converted a system from sha1 hashing to bcrypt recently, and in order to make it seamless for the users, we didn't require resetting passwords. We ran all the existing sha1 hashes through bcrypt, and added a column to the database, migrated = false. When logging in, we check that column, then do either verify_bcrypt(passwd) or verify_bcrypt(sha1(passwd)) (to avoid the login-with-hash problem Mark described). Finally, on successful login, if migrated was false, we do bcrypt(passwd), replace the old hash, and set migrated = true. – Alex Apr 15 '16 at 5:58
  • @Alex Bingo! Good job. – Mark Buffalo Apr 15 '16 at 11:55
3

To your first question:

It depends on the application. In most cases, the password is sent to the server in clear text (and this is insecure if the application uses an unencrypted protocol, such as HTTP). The server, however, might store a hashed value in the database. If it does, the application calculates the hash value of the clear text password received from the user and matches it against the value stored in the db.

To your second question:

The server expects a password input: X. The attacker somehow finds out h(X). Then if he would enter this hashed value to authenticate, the hash function would be ran once again against h(x), so he wouldn't be able to get in since h(x) does not match h(h(x)).

1

As others have pointed out, the usual way for this to work is that the client sends the cleartext password, but over a secure channel (which uses reversible encryption, not hashing), and that password is then hashed server-side with the same salt as the stored hash.

       Client      <----- Secure link ----->     Server
Cleartext password     Encrypted password    Hashed password
  1. Client has the cleartext password.
  2. Client encrypts password (via TLS)
  3. Server receives encrypted password, deciphers it (via TLS)
  4. Server hashes password with same salt as stored in database for this user
  5. Server compares computed hash with stored hash.

Protection:

  • against eavesdropping (due to the use of a secure channel)
  • against MITM attacks (if the TLS session is properly setup, certificates properly checked, etc.)
  • against password database theft

Now the issue is when you have to send a password over a channel that does not provide encryption, as could be the case for low-level protocol (for instance PPP or 802.1X). The traditional choices (for PPP) were:

  • PAP: send the password in clear text, which can then be hashed server-side for comparison with the hashed password database

  • CHAP: use a challenge-response protocol and send a hashed version of the password (combined with the challenge and an id) over the link, but this required the server to store the plaintext of the passwords

So the former protects against password database theft, but not against eavesdropping, while the situation is reversed for the latter.

Putting aside EAP protocols which actually set up a TLS tunnel (PEAP, EAP-TTLS, EAP-TLS...) to secure the password exchange, there is one alternative protocol that enables sending a non-reversible "hash" of the password to a server, which also store a "hash" of the password: SRP, and more generally "augmented PAKE" protocols.

0

An attack based on simply resending captured data (e.g. a password that was hashed by the client) without needing to decrypt the data is known as a "replay" attack. The standard mitigation for this attack is to include what is known as a "nonce" in the hashed data, this could be a random number provided by the server, or a reasonably high-resolution timestamp generated by the client. The server will then check that the nonce is valid or within some acceptable range, and if it is wrong or too old it will reject the request. Detailed discussion can be found over here What is the use of a client nonce?

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