Since the recent problems with GitHub and Twitter:
- GitHub Accidentally Recorded Some Plaintext Passwords in Its Internal Logs
- Twitter Admits Recording Plaintext Passwords in Internal Logs, Just Like GitHub
I was wondering, why isn't the best practice to bcrypt the password both on the client and the server? Since I won't change anything that already are the best practices for the server side (salt, strong hash, HTTPS), it can only be safer. The server would consider the already hashed password as the password, and would hash it again before store it.
- In case I log the entire request when an exception is thrown, if an exception happens in the login/signup request, I would never get access to the user plaintext password
- I know that if somebody have access to these only-client-side-hashed passwords, either by MITM (which a lot of companies do in their private networks replacing the SSL certificates) or by logs or a malicious server administrator, they would be able to use it to authenticate in my site, but wouldn't have access to the plaintext password, so it would never compromise the user's account in other sites and services (even for those users that reuse their passwords)
I was looking to resolve a similar issue where plain-text password can be logged on the server. The conclusion is that you should always additionally hash the client password if able.
Here is some articles about client-plus-server hashing:
Client-Plus-Server Password Hashing as a Potential Way to Improve Security Against Brute Force Attacks without Overloading the Server
Salted Password Hashing - Doing it Right
Specifically, see:
In a Web Application, always hash on the server
If you are writing a web application, you might wonder where to hash.
Should the password be hashed in the user's browser with JavaScript,
or should it be sent to the server "in the clear" and hashed there?
Even if you are hashing the user's passwords in JavaScript, you still
have to hash the hashes on the server. Consider a website that hashes
users' passwords in the user's browser without hashing the hashes on
the server. To authenticate a user, this website will accept a hash
from the browser and check if that hash exactly matches the one in the
database. This seems more secure than just hashing on the server,
since the users' passwords are never sent to the server, but it's not.
The problem is that the client-side hash logically becomes the user's
password. All the user needs to do to authenticate is tell the server
the hash of their password. If a bad guy got a user's hash they could
use it to authenticate to the server, without knowing the user's
password! So, if the bad guy somehow steals the database of hashes
from this hypothetical website, they'll have immediate access to
everyone's accounts without having to guess any passwords.
This isn't to say that you shouldn't hash in the browser, but if you
do, you absolutely have to hash on the server too. Hashing in the
browser is certainly a good idea, but consider the following points
for your implementation:
Client-side password hashing is not a substitute for HTTPS (SSL/TLS). If the connection between the browser and the server is
insecure, a man-in-the-middle can modify the JavaScript code as it is
downloaded to remove the hashing functionality and get the user's
password.
Some web browsers don't support JavaScript, and some users disable JavaScript in their browser. So for maximum compatibility, your app
should detect whether or not the browser supports JavaScript and
emulate the client-side hash on the server if it doesn't.
You need to salt the client-side hashes too. The obvious solution is to make the client-side script ask the server for the user's salt.
Don't do that, because it lets the bad guys check if a username is
valid without knowing the password. Since you're hashing and salting
(with a good salt) on the server too, it's OK to use the username (or
email) concatenated with a site-specific string (e.g. domain name) as
the client-side salt.
After researching, it seems that there is a clear security benefit in hashing the client as well. If the password over HTTPS is compromised or if the password is logged on the server, then the plain-text password could not be easily reused on the user's other accounts (many users reuse their passwords).
The only possible downside is client performance, and server-side password validation. A user can manipulate your client JS and submit a "weak" password. The server wouldn't know any better. But I think this is a small issue, and it relies on people intentionally modifying their client code in order to weaken their own security.
Client side hashing can be done, but we should think about what we really achieve.
What you probably want to achieve is, that the password cannot be read by an attacker, when it is sent over the (hopefully encrypted SSL) connection. If an attacker can intercept the traffic, it is very likely that (s)he can alter it as well, and therefore can strip away any JavaScript doing the client side hashing. Then the whole protection comes from server side hashing.
What you can achieve is, that you can reduce the server load, because you let the client do the heavy calculation. If you could guarantee the integrity of the client, you could then do key-stretching on the client and use a fast hash on the server. This can be an option in case of an installed app, but is not recommended for a website, because one cannot guarantee the integrity of the client, and because JavaScript is usually slower, so you can do less rounds.
You would get a small benefit if an attacker can only listen to the traffic, but cannot alter it. The time you are willing to spend on hashing must then be split into a client part and a server part (one cannot let the user wait forever). The server time must be long enough to guarantee security, that leaves little time on the client. If you use a too fast hash on the client, then an intercepted password-hash is still in the scope of brute-forcing (though it is a hurdle an attacker has to take).
So in short, it is usually not worth the trouble, the advantage is too small and the time is better invested in hashing-time on the server.
Any hash (including bcrypt
) requires secret salt - read here for more details. If that salt is lost, the client will not be able to create the same hash - which is the same as losing the password. So you need to create a mechanism that will allow all your client to get the salt securely. And you need to make sure that a hacker will not be able to get this salt. This is pretty complicated to achieve.
Another thing to consider is the end user device limitations - for example, Android device has pretty weak CPU, and are far less powerful than the average server. As the main strength of bcrypt
is the time taken to compute the hash, you need to choose parameters such that a good server (maybe even with a GPU), will compute it in a slow time (let say, > 1s for passwords with 20 chars). This what make is so hard to create those rainbow tables.
So, unless you can guarantee that all your users are running on strong enough devices, it is not recommended to do bcrypt
on the client side.
The problem with this scheme is that it requires the server to trust the client. In particular, it assumes the client will always actually hash what the user types in. If we break this assumption, as an intruder might, problems start to pop up.
Bob has a list of (single-hashed) passwords from your server logs. These aren't plaintext passwords, but they aren't the double-hashed passwords from your password file. But let's say he makes one small change to your client: he takes out the bcrypt() line, so it no longer hashes whatever he pastes into the password field before sending: instead, it just sends the raw text.
Then he starts sending logins. Now your server sees usernames and single-hashed passwords (because that's what Bob typed, because that's what Bob knows). It assumes this is the usual client, so it goes on to hash the password again and check against the double-hashed password in its file... and it's been hashed exactly twice, so it matches. Bob didn't know the plaintext password, but by modifying the client he made it so he didn't need to know it.