We have a website where users need to log in to access privileged information. Obviously we are using SSL, but I also want to avoid plaintext passwords from accidently ending up in server logs, or wandering eyes of administrators. Therefore, I want to implement client-side hashing using Javascript.

Furthermore, I want to avoid revealing pairs of users with identical passwords, thus use a user-specific salt. Lastly, I would like to add a random challenge (aka nonce) into the mix to ensure an eavesdropper* can not use the final hash as the new "password". I will make an additional roundtrip to the webserver to obtain the salt and challenge before starting the password hashing. *) Yes, we are using SSL, but as a defense in depth I would like to make the implementation as solid as possible.

Especially the challenge is challenging. The only feasible implementation I know of, is first calculating the final password hash (using bcrypt, scrypt or PBKDF2) and then do one additional sha256_hmac round using the challenge. On the server-side we fetch the password hash from the database and perform the same additional sha256_hmac and compare the result.

However, the above would imply that the full password hash calculation must be done client-side in Javascript, because hashing in the challenge should always be the last step. On a legacy(?) iPhone 3G, only 6 bcrypt rounds, or 5000 PBKDF2-SHA256 rounds can be done within 2 seconds which is the maximum delay we can tolerate.

Question #1: Am I correct that nowadays bcrypt should be at least 12 rounds, and PBKDF2-SHA256 at least 5000 rounds? So I should choose the 5000 rounds of PBKDF2-SHA256?

Question #2: Do there exist challenge-response mechanisms that can operate earlier in the process (e.g. on a once hashed password), so I can leave the bcrypt/PBKDF2 hardening to the webserver?

Question #3: Assuming no such challenge-response mechanism exists, is it better to abandon my challenge-response requirement and only apply a user-specific salt client-side and do thorough hardening server side? Would that result in an overall stronger system than using a challenge-response mechanism and less rounds?

EDIT: To summarize the long trail of comments below about the security of client-side hashing in general: the general opinion seems to be that the added complexity is not worth it and it is better to put the effort in auditing the server-side code and limiting server-side access for untrusted staff.

When I implemented somethnig similar before I used hash(hash(username, password), session_id) - which avoids some of the complexity in your solution - the username is effectively a salt for the paword hash - and this value is stored serverside, and the sesison id is already available at the client - although using a value independent of the session id might be an idea if you erstrict javascript access to the session cookie.
–
symcbeanJul 12 '12 at 13:38

You didn't mention on your question what serverside language you are using but you mentioned PHP in a comment below. There is now an excellent php+js demo of SRP by Ruslan Zavacky over on github at github.com/RuslanZavacky/srp-6a-demo
–
simbo1905Jun 5 '14 at 13:41

4 Answers
4

I think you're wasting your time and adding needless complexity. I don't think the reasons you give are sufficient to warrant this kind of client-side password hashing mechanism.

Instead, I suggest keeping it simple. Send the password over a SSL-encrypted link. When it comes to security, simple is good. Needless complexity is the enemy of security, because it introduces the opportunity for bugs and mistakes.

Save your energy for other security tasks that will address more realistic threats. There's a limited amount of time and energy and budget one can spend on improving security; best to spend it on the places that will yield the greatest bang for the buck (the greatest improvement in security, given the investment). I think you'll find there are other ways you could spend your hardening your system that will improve security more.

If you're worried about whether the server-side code is handling the password appropriately, then review that code carefully: it should be contained to within a very small chunk of code. This should take care of risks like, e.g., the server logging cleartext passwords: you just read every line of code that's involved with password checking or that reads the password, make sure the first thing it does is to hash the password appropriately, and make sure that all it does is check the password for validity and return true or false. If your system is well-designed, this should be pretty straightforward to verify through code review. If the code isn't well-designed, maybe now would be a good time to fix that.

What about this simple attack scenario: the attacker manages to sniff all traffic going into your server for a few months and then they manage to compromise the server and copy all useful data, including the private key. Hopefully you detect the intrusion quickly and cut off their access but they still have both the encrypted traffic for the past several months and the private key used to protect that traffic. So, if hashing is being done on the server-side now they can get all plain-text passwords, which would not have been the case if you hashed on the client-side.
–
resistorJul 16 '12 at 1:51

@resistor, it is possible in principle, but in practice this is a pretty uncommon scenario. It is rare for bad guys to be in a position to log all of the traffic going to your server over a period of months, or for them to have an incentive to do so. If this is a major concern for you, you could look at forward-secure ciphersuites for SSL (something that is easy to set up with a single line of configuration), but I suspect for most sites it is not worth worrying about. And in the unlikely event that this situation arises, you do still have options: e.g., resetting all your users' passwords.
–
D.W.Jul 16 '12 at 2:16

@resistor - How exactly are they going to sniff encrypted traffic? If they have physical server and/or the ability to instal forge certificates to perform a MITM attack against your users then you have bigger security concerns
–
RamhoundJul 17 '12 at 18:22

@D.W. thanks, that is indeed a better way to address this.
–
resistorJul 31 '12 at 23:09

1

Agreed. Definitely rely on SSL to protect passwords coming from the client-side. 1) Doing otherwise would require a large code payload to implement all the encryption technology you'd need to pull it off. 2) You can't trust anything that happens on the client-side anyway. Maybe you can cover a MITM case, but what about a keylogger? 3) You know server CPU power. Worry about this stuff, first: ericleads.com/2013/08/…
–
Eric ElliottOct 6 '13 at 20:08

Thanks for the suggestion. This looks indeed useful. Unfortunately, I was only able to find two Javascript implementations (srp-js, which appears to be abandoned/incomplete), and Clipperz (which is bulky and requires a 430 KB Javascript download each time a user tries to log in). Native support by the major browsers is currently limited to Firefox. I read this Nonetheless does this seem the way to go. I keep looking for a lightweight SRP implementation in Javascript + PHP.
–
Jason SmithJul 12 '12 at 22:04

I studied the protocol. Am I right that one still needs to calculate the PBKDF2 client-side? If so, using SRP only helps the challenge-response, but does nothing to improve the key stretching.
–
Jason SmithJul 13 '12 at 17:31

Sorry for the sloppy wording. Let me rephrase that: SRP looks like the perfect solution for the challenge-response problem, but still requires client-side password hashing. Thus I still have the dilemma of choosing between less hashing rounds client-side, or skip the challenge/response and do all hashing rounds server-side.
–
Jason SmithJul 14 '12 at 11:07

I would be wary of any home brewed protocol. I would also be wary of any client-side hashing based authentication protocol. Your best bet is to study NTLM Authentication and the LARGE NUMBER of attacks against this protocol. Such as the NTLM weak nonce vulnerability.

You do have the option of doing some hashing client-side (say, 2,000 PBKDF2_SHA-1 rounds), and then once the result of that gets to the server, run it through another large set of hashing (say, 300,000 rounds of PBKDF2_SHA-512), and compare that final value.

Your database stores only the final double-PBKDF2 value.

Your web log files and/or possibly weak SSL sessions use the intermediate single-PBKDF2 value. While that intermediate value is certainly much, much weaker than the final value, it's still quite a lot stronger than cleartext.

Note that you also need to check passwords users propose when changing their password/selecting a new passwords against a normal cracking dictionary with some rules (lowercase both to eliminate case games, add numbers from 1 to 1000 after the word, add dates after a word, basic 1337 speak translations, etc.) to prevent the "strong" password "P@$$w0rd" (upper case, lower case, symbols, and numbers - 8 characters long, it must be really strong!) from showing up.

The more arbitrary complexity rules you add to a password, the more likely it is for a user to just write it down on a sticky note, which is why I think enforcing too many rules is a terrible idea.
–
NullNov 2 '12 at 20:32