1

This is mostly a thought experiment for client/server communication, and I want to know the flaws.

When a user account is created (with U as the username and P as the password,) I generate a random salt (S) and store these values in the database (on the server-side): U, S and H which is computed as H = hash(U+S+P).

When authenticating a user:

  1. The client first "attempts an authentication" by sending U to the server.
  2. The sever creates a challenge code (R) for that user and stores it in the database, along with the time this challenge would expire (T) and sends R and S back to the client.
  3. The client calculates its own version of H (by hashing U and S and P together; it has U and P, and received S from server.)
  4. The client then computes a new value G as G = hash(H+R) and sends this G back to the server (actual "authentication request".)
  5. The server hashes its own H together with R to reconstruct G and authenticates the user iff its G matches the value received from client and T has not passed yet.
  6. Whether authentication succeeds or fails, R is removed from the server database.

The flaws I'm already aware of are these:

  • When creating an account, the password is transmitted in plaintext. This can be mitigated using a dedicated account-creation service that employs SSL for creating accounts.
  • Attempting an authentication will result in some resources being allocated on the server. This can be mitigated by allowing only one (or a fixed number) of challenge codes per user.
  • Trying to discover valid usernames and salts by generating many authentication attempts; which the server can foil by sending a random salt and challenge code even when the user does not exist.

Here are my specific questions:

  1. What am I missing? Where is the disaster waiting to happen?!
  2. What can I do to make this more secure/better? Other than using SSL and just sending plaintext passwords (plaintext only from the perspective of my application.)
  3. I'm thinking of SHA-256 for all hashes. Are there any flaws in the SHA-2 family? Is hashing the values once enough for generating H and G?
  4. For generating H and G, are other permutations of values better? e.g. P+U+S, R+H, etc.
  5. What conveniences am I denying my users? (Apart from their authentications now needing two round trips to the server.)

UPDATE: Ultimately, I want to do this:

After a successful authentication, the server generates a session key K as: K = hash(H+F) where F is a random string. F is sent to the user and K is retained in the database. The client reconstructs K using the received F and encrypts all communications with the server henceforth with a symmetric cipher (e.g. AES) with K as key.

  • 7
    DO NEVER USE PLAIN HASH-FUNCTIONS FOR PASSWORD-HASHING. Use something purpose-built like scrypt/PHC-Winner/bcrypt/PBKDF2. Some PHC canidates offer protocols for password-authentication where the client has the workload. And you may want to look into this paper. And of course: Don't roll your own crypto. – SEJPM May 25 '15 at 20:55
  • Concerning your update: TLS-PSK with the ECDHE_PSK_* cipher suites may be better. – SEJPM May 25 '15 at 21:00
  • 2
    @SOJPM: Thank you, but as I mentioned, this is a thought-experiment and learning project. I'm not going to use this in production. (I'm guessing "Don't roll your own crypto", which is very sensible, is a motto around here and other security forums.) Aside from the already provided links, can you give me any pointers on why shouldn't one use plain hash functions for password hashing (even salted)? – yzt May 25 '15 at 21:03
  • 3
    This is the often cited canonical answer. In short: standard hash-functions aren't built for this purpose and possible (low-entropy) passwords can be tried extremely fast so that the password can be cracked within minutes/hours/days. And of course by building your own scheme for password-hashing you're rolling your own crypto, which is generaly considered bad as chances are you're getting it wrong. As a sample look at this. – SEJPM May 25 '15 at 21:08
  • 2
    @yzt The main reason is that they are fast to evaluate. Passwords are low-entropy in practice, so brute-forcing a plain hash is easy. That said, you could maybe replace the first hash with a good password hash to compute H. – cpast May 25 '15 at 21:09
8

Your main problem can be summed up as: where's the SSL ?

Any eavesdropper, observing the exchanges, will obtain enough information to run an offline dictionary attack: the attacker will be able to "try passwords" by recomputing the relevant hash values and see if they match that which was observed. This is "offline" because the attacker can do all of this on his own computers, without interacting with the client or the server. The attacker can do all of this at the speed of his own machines, and, thanks to the fact that usual hash functions map very well to what modern CPU and GPU can do, he will be able to try billions of passwords per second. Not many passwords will last long under these conditions.

You could try to do some prior Diffie-Hellman to get a shared encryption key, and encrypt everything with that, but you have just moved the problem to that of making a Man-in-the-Middle attack: you think you talk to the server, but you are really talking to the attacker, with whom you do that DH key exchange. The attacker is then back to the previous situation, and can observe the hashes that allow him to run an offline dictionary attack.

The normal solution is to first setup a secure tunnel to the server, that at least guarantees to the client that he is talking to the intended server and nobody else. This is called SSL (or TLS, which is its "standard" name). Once you have such a tunnel, you can use a much simpler "show the password" authentication method, and the tunnel will also ensure that all subsequent data exchanges will be linked with that authentication (i.e. attackers won't be able to hijack the socket after the authentication steps).

SSL assumes that the client has a way to verify the server's identity -- that's the point of the server certificate. If you want to do without that certificate, and achieve password-based mutual authentication of the client and the server, but without revealing anything that would allow attackers to run offline dictionary attacks (even active attackers that try to impersonate the client or the server), then you need one of these nifty password-authenticated key exchange algorithms. There again, use SSL, with SRP cipher suites. The only known protocols that succeed at such certificate-less password-based authentication without offering offline dictionary attacks use some maths; you won't achieve that by simply using hash functions.

Regardless of the authentication protocol, you still need a secure password hashing function for whatever you store on the server side, to avoid transforming a read-only partial intrusion (e.g. through some SQL injection) into a password-breaking fiesta.

And, of course, in cryptography, "do-it-yourself" is the fastest road to Hell.

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.