Password Hashing In The Browser


There are rarely new ideas in cryptography - and I doubt this idea is particularly innovative - but I thought it would be worth discussing.

When I want to log in to a system on the web, I have to send that system my password. It is (one hopes) encrypted in transmission, but once it arrives at the server there is a brief window where the server holds my password in plaintext. It then hashes it and compares it to the hashed value it has stored in its database.

A malicious party could intercept the password either by sitting between me and the server - or by compromising the server itself. Or, a corrupt sysadmin could steal the password. Or, incompetence could lead to a range of easily cracked passwords leaking out.

Once you know my email address and found out my password - you can try it against of range of services.

So, how do we send the password without sending the password?

Suppose we have an HTML password field which insists on a minimum of 6 characters:

<input type="password" pattern=".{6,}">

Let's now add "encrypt the password with this algorithm before sending it":

<input type="password" encrypt="sha1" pattern=".{6,}">

Now, obviously SHA-1 isn't secure, but it means that I don't send hunter2 to the server, I send f3bbbd66a63d4bf1747940578ec3d0103530e21d.

The server should not store the hash directly - but rather encrypt it first.

We could take it any further:

<input type="password" encrypt="bcrypt" salt="abc..." rounds="4" pattern=".{6,}">

That's probably a bit overkill - and you'd need to make sure you used the same configuration every time someone logs on - but it means no one other than you ever sees the password.

Is this worth taking further? Of course, if you can't trust a company to securely store your password properly, it's unlikely they'd implement this properly.

6 thoughts on “Password Hashing In The Browser

  1. This is an interesting idea, but if I've understood it I think there are two flaws - both based around the fact that essentially now your password is the plaintext hash as far as the server is concerned.

    If someone intercepts the transmission, therefore, they don't need your actual password : it's enough to replay the message with the hash they captured.

    If they have access to the server, then again they can get the hash and use that to authenticate, or relatively easily crack the majority of those hashes to reveal original passwords given they're not individually salted.

    To be more secure than the status quo, I think you'd need a process more like:

    1. Browser sends a request with just the username.
    2. Server locates the user's personal salt in the database, and generates a second unique salt specific for this attempt. It stores the one-time salt server-side in the user session with an expiry time.
    3. Browser hashes the password with the user's salt [this will produce the same hash as stored on the server, but it never leaves the browser's secure memory area], then hashes again with the one-time salt.
    3. Browser sends the authentication request with the username and one-time-hashed password.
    3. Server locates the one-time salt from the user session, and uses it to hash the user's stored password hash to verify that the result matches what the browser sent.

    This makes both replay and hash cracking attacks much more difficult, since you can never just present the same values that were sent for a previous successful login, and every user's hashes are individually salted so rainbow tables etc won't work.

    But it's quite a bit more complex to implement, and may be overkill assuming proper server-side and connection security is in place? And if they're not then as you say there's limited chance someone would get this right.

    You could potentially simplify a little by storing the user hash in browser local storage on a trusted device, and rendering the one-time session hash in the original login page, so you'd skip one http request on a device that a user had previously used to authenticate.

    1. In your description you mention in #3 (the first of them) that the browser hashes using the user's salt (not the one-time one) and then afterwards the one-time salt. How does the browser get the user's salt in the first place? Is it returned in the response to the request with the username?

      So the user enters password and we send: hash(userOneTimeSalt + hash(userSalt + password)) is that correct
      What do we have in our db? hash(userSalt + password) I assume? Since the one-time salt is just to mask the hashed password.

      Deny request if hash(userOneTimeSalt + db.hashPassword) != double hashed password from login attempt.

      When the user is created it is a bit different isn't it? We would pass the users salt that we just created based on the username request. The browser hashes the password and sends this hash (without the extra level of hashing) and we store that value directly as the password?

  2. > A malicious party could intercept the password either by sitting between me and the server - or by compromising the server itself. Or, a corrupt sysadmin could steal the password. Or, incompetence could lead to a range of easily cracked passwords leaking out.

    If the malicious party can MITM your communications with the server, or compromise the server, or be a sysadmin then they can replace the fancy `` with `` and get your password anyway.

  3. In principle the advantage of this system is that, even if your credentials for one site are leaked, it doesn't leak the password you use on any other site. In theory at least, it could be an incrementally more secure system than we currently have.

    Unfortunately we already have a lot of companies - big, reputable companies that can afford developers and should know better - doing a pathetically bad job of storing passwords. It's become the norm to hear that company X's database has been hacked and 5 bajillion passwords have been leaked -- and it's a pleasant surprise to hear the company Y's database has been hacked and 0 passwords have been leaked because they were doing their hashing properly. I can readily see them using something like this to hash passwords on the client side and missing out on the tiny detail that the delivered hash has to be hashed again on the server.

    Ultimately, this is bringing a second sledgehammer to crack the same nut. The only solution will be for us to find a way of securing ourselves online through something other than passwords. It's a shame that efforts like OpenID, Persona etc have yet to get any traction. It'll take a coalition of the big identity providers (@gmail.com, @hotmail.com etc) coming together to integrate something like SSL client certificates or Kerberos, and making it work seamlessly in the browser.

  4. It is interesting to consider the potential benefit, but I don't see how this really helps at all.

    If someone is controlling your access to a service (either MITM or a corrupt sysadmin), then it is pretty much game over. They could just send down a login form without the hashing client side. Recommending that people encrypt a SHA-1 hash of the user's password in their database is nuts. There are well understood and proven solutions to this problem - check out OWASP or NIST's updated advice. Unfortunately you just have to rely on the service provider to just do a good job.

    The only sane solution is:
    * rely on federated authentication where possible - I trust Google/Microsoft/Facebook to do a good job of storing my password (they have more to lose)
    * the service should follow OWASP guidance (salted PBKDF2/bcrypt with stretching) and offer 2FA based on TOTP.

    The top answer here gives a much more succinct appraisal.
    https://security.stackexchange.com/questions/53594/why-is-client-side-hashing-of-a-password-so-uncommon

  5. I am not very good with web securities but I wish to suggest a way around this password hashing issue.
    First of all, on the registration page, generate a random salt (client_salt) to hash the password then send this salt in a hidden input to the server.
    On the server, store this random salt from the client side and then generate another random salt on the server side and use it to has the hashed password before storing. That is, stored_hash = hash(server_salt + client_hash).
    When authenticating login on client side, browser sends the user_name to server to retrieve the two salts and then a one time salt which is generated and stored in the server sessions. Now in the client side, password which is stored in browser's secured memory is hashed twice with the client_salt and then the server_salt. The hash is then hashed with the one time salt before transmission to the server.
    On the server once again, the stored_hash is hashed with the one time salt stored in the sessions before hashed passwords are compared.
    This would ensure that on the registration page, if someone intercepts the password used for the registration, only the hashed form of it would be available which is also not useful since it would be rehashed again.

Leave a Reply

Your email address will not be published. Required fields are marked *