When storing user's passwords that you need to verify against (but not use as plaintext) the current state of the art is:
- Hash the password
- Use a salt
- Use a slow hash function - bcrypt, scrypt, etc.
This provides the best possible protection against an attacker who has stolen the password database. It does not solve other issues like phishing, malware, password re-use, etc.
But there is one remaining problem: the slow hash function can allow a denial of service attack. A single request burns a lot of CPU, which makes a DOS possible with a relatively small number of concurrent requests, making it difficult to use defences like IP throttling.
Given the improving performance of JavaScript in browsers, it may now make sense to do this in the browser. I'm assuming here that the site is using SSL, so the JavaScript is delivered securely. If you're not using SSL, then using a slow hash function is unlikely to be a priority for you. There is at least one JavaScript implementation of bcrypt. But using this in a simple way would introduce two problems:
- The client needs to fetch the salt from the server. This introduces latency on login and, unless care is taken, can reveal whether a particular user account exists.
- If hashing is done purely on the client then the benefits of storing hashes are lost. At attacker who has stolen the password hashes can simply login using the hashes.
However, I think there are acceptable solutions to both of those problems:
- The salt can be generated as hash(server_salt + user_name) - where server_salt is a random number that is unique to the server, public, and the same for all users. The resulting hash appears to have the required properties of a salt.
- The server should do a single, fast, hash operation on the hash it receives. As an example: the server stores SHA-256(bcrypt(salt, password)). The client sends bcrypt(salt, password) then the server applies SHA-256 and checks the hash. This does NOT allow an attacker to conduct a fast offline brute force attack. They can do a fast brute force of SHA-256(password) because password has a limited amount of entropy - 2^50 or 2^60 or so. But a 128-bit bcrypt(salt, password) has entropy or 2^128, so they cannot readily brute force it.
So, is this a reasonable and secure approach?
I am aware of the general advice to "don't roll your own crypto". However, in this case, I am attempting to solve a problem that is not solved by off-the-shelf crypto. For some basic credibility, this has been looked at by John Steven (a recognised expert in the field) with positive outcome from a "brief" analysis.