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.

4 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.

What do you reckon?

%d bloggers like this: