19

I am migrating an old application which used MD5 hashing to Spring Security with BCrypt encoding of passwords. I want to encode the password on new user creation page, change password page and on login page before it is sent to the network.

I know HTTPS can solve the problem, but I am still instructed to encode the password before sending it over network as per our organizational guidelines.

What could be the best possible solution to do hashing of the passwords using JavaScript?

Just to explain it further, I am using JCryption API for encrypting the password using AES, so the value transmitted over network is AES(SHA1(MD5(plain password))) now I want to replace MD5 with Bcrypt only. Rest of the things remain unchanged. Will this approach work against "Man in the middle attack" ?

Amit
  • 301
  • 1
  • 2
  • 7
  • AES needs a key, shared by the client and the server, how you handle it in secure way ? – fmgp Jul 08 '15 at 12:18
  • 2
    To be clear are you asking about client side hashing protected by https or do you believe you can magic away the need for https by doing client side hashing? In the former there is really no point but it is still secure, the latter is simply and can never be made secure inside a browser. – Gerald Davis Jul 08 '15 at 13:04
  • 2
    Client-side hashing is by no means a protection against man-in-the-middle-attacks!!! The attacker will simply grab `AES(SHA1(MD5(plain password)))` and use it to log into your application. – piet.t Jul 09 '15 at 06:06
  • Key for AES is generated once login page is loaded so even if someone gets the AES(SHA1(MD5(plain password))) they won't have the key and so the decrypted value will not be same. Unless they get the jssessionid and challenge, which obviously is a weakness. – Amit Jul 09 '15 at 06:44
  • 2
    The default system you describe takes in a "plain password" and validates it. The proposed new system instead takes in a "encrypted password" and validates it. The only possible vector I can imagine you wanting to defend here is a man-in-the-middle attack, and this is no protection because instead of fetching the "plain password" off the wire, the attacked will fetch the "encrypted password" instead. The fact that it doesn't reveal the "plain password" is meaningless, because they no longer need the "plain password" to authenticate, since the server now accepts an "encrypted password". – Nick Coad Jul 09 '15 at 07:00
  • You need to provide a server nonce and client nonce that is used in encrypting the password, like Digest authentication, but it would be more secure to replace MD5 in RFC 2617 with bcrypt. http://tools.ietf.org/html/rfc2617#section-3 – Motomotes Dec 28 '15 at 16:29
  • 1
    @NickCoad RE "The fact that it doesn't reveal the "plain password" is meaningless" Why is it meaningless? Many users will re-use their password for multiple sites/apps. Giving the attacker a plain password is silly if it can easily be prevented. (Obviously you'd want to hash it again on the server) –  Dec 29 '16 at 10:46
  • @JoeRocc in the context of man-in-the-middle attacks, hashing or unhashing the password makes no difference. Reread my statement in context of the rest of my comment and you'll see what I mean. – Nick Coad Jan 04 '17 at 22:58
  • 1
    @NickCoad Ah, gotcha for some reason I read it as "meaningless in general" rather than "meaningless in this context" –  Jan 05 '17 at 01:02
  • What you do is : H(nonce||timestamp||K(password||salt)) nonce, timestamp and salt are "public". password is provided by the user and private. H and K are hashing function. In the database, the password is stored as K(password||salt). The nonce can only be used once per interval of 5 minutes and the Timestamp must be within the 5 minutes of server clock. This ensure that it can't be replayed. – programaths Sep 07 '17 at 19:15

5 Answers5

48

I know HTTPS can solve the problem, but I am still instructed to encode the password before sending it over network as per our organizational guidelines.

This really defines your situation.

Basically, you have a simple solution that you should use anyway (use HTTPS), if only because without HTTPS an active attacker could hijack the connection after the authentication step, regardless of any "encoding" and hashing you use for the password (an attacker who is in position to do a Man-in-the-Middle attack will succeed regardless of how much you ritually dance with hash functions and encryption). Then, you also have a guideline that is both idiotic, and administratively unavoidable. Your problem is then: how can you do things properly, and still comply with the "guideline" ?

Note that the guideline makes little sense at several levels. Not only does the lack of SSL makes the protocol vulnerable to hijack; but also any kind of "encoding" does nothing good against passive attackers either. The reason is that if the protocol is "the client shows the 'encoded' password" then a passive eavesdropper will simply look at that "encoded password" and send it himself -- thus, that kind of encoding does not actually improve security in any way. It just makes some people feel more secure because they understand cryptography as some kind of barbecue sauce ("the more we sprinkle everywhere the better it gets").

What I suggest is that you do the following:

  1. First, try to sell the idea that "HTTPS" incarnates the 'encoding'. In other words, by sending the password inside a SSL tunnel, you are already complying with the guideline, since the password is duly encoded (and, really, encrypted), along with the rest of the data.

  2. If the stupidity infestation is more serious and some auditor (let's call him Bob) still throws a fit about your lack of "encoding" then try applying some reversible encoding on the client side. E.g. before sending the password, apply Base64. This means that if the password is "bobsucks", what will be sent (within the SSL, of course) will be "Ym9ic3Vja3M=", and Bob will be happier, because the latter string is obviously a lot more secure.

The important point here is that since the requirement does not make sense, the solution won't make much sense either.

Tom Leek
  • 168,808
  • 28
  • 337
  • 475
  • I would add as a #3 last resort option, if that they still require client side hashing it can be done but https secured communication channel is a prerequisite. client side hashing + https can be made secure but outside of zero knowledge scenarios it is more complex with no added benefit however if the company/client insists it be done that way the ethical thing is to inform them "ok we can do that but we still need https". – Gerald Davis Jul 08 '15 at 13:02
  • @GeraldDavis That's basically what #2 is above. Only difference is you say to hash it and answer says use a reversible encoding. ;) Effectively the same either way. – jpmc26 Jul 08 '15 at 16:39
  • 2
    I would go a step further on your "guideline makes little sense" — not only does transmitting the encrypted password not add security, it can easily *reduce* it. Suppose someone steals your password database full of encrypted passwords, which are identical to the ones you send on the wire (it's "encoded", right?) Now an attacker *doesn't even need to break the hash to gain access to users' accounts*; they can just log in directly with the credentials they got from the DB, defeating half the purpose of encrypting passwords. – hobbs Jul 09 '15 at 02:07
  • 1
    @hobbs That's more a problem with being ill informed about security. The problem isn't that you're hashing/encoding it before sending; security is reduced because of the lack of action (driven by false beliefs) from the implementer. The problem there is the false beliefs about security. In practice, that affects how you solve the problem. Step 1 is educate so the implementer knows what to do. Then when fixing, you can just add server side hashing (and TLS if not present); the need to eliminate the client side work isn't pressing until it starts causing bugs or extra work or other costs. – jpmc26 Jul 11 '15 at 00:12
  • +1 for the last statement, which is unfortunately applicable way to often! – Maverick283 Dec 26 '18 at 20:42
25

You have no security without authentication

Just to explain it further, I am using JCryption API for encrypting the password using AES, so the value transmitted over network is AES(SHA1(MD5(plain password))) now I want to replace MD5 with Bcrypt only. Rest of the things remain unchanged. This approach works even against "Man in the middle attack".

No it does not. I can't express this strongly enough. Now I may not convince you to use https (you might not even have a choice) but I hope I can convince you that it is absolutely insecure and any belief that you have about it providing some security is incorrect. If forced to move forward without using TLS/SSL you can at least make it very clear to everyone involved that the system is vulnerable and should be described as "easily circumvented feel good security". A script kiddie running a MITM hotspot at your local starbucks could spoof your users and steal credentials or hijack sessions so it isn't anything which requires sophisticated skills or complex attacks.

If the communication channel is insecure you should have no expectation that the code you send is the code that is actually run by the client. There is a reason it is called https (secure http) not just httpe (encrypted http), security is more than just encryption. Encryption communication without authentication is pointless.

Some things to think about:

  • Without https how do your users even know they are on your login page?
  • How do you know an attacker hasn't modified the login page sent to the user to send him a plaintext copy of the credentials before encrypting it and sending it to you?
  • You are trying to encrypt the double hash of the password how are you going to securely transmit the encryption key?

What if I do my own custom authentication in addition to the encryption

It isn't quite that simple. Bootstrapping a secure channel over an insecure one is not a trivial problem. In theory you could build a custom protocol which replicates all the aspects of https including protocol negotiation, server authentication, public key infrastructure, key exchange, encryption, message integrity and revocation (and I am sure a lot more I forgot). Once you got done it would be just as large and complex as TLS. Let's also assume it had less flaws than any TLS implementation and you could do it in less than a couple of years, you would still have an almost impossible problem. How are you going to get this client side components to the client? "Oh sure have the client download it securely ... er wait". If bootstrapping secure communication over an insecure channel seems easy then honestly you haven't looked deep enough, it is turtles all the way down.

Now let me be clear, TLS has a lot of problems, and the CA system has a lot of problems too. I certainly am not going to pretend it is perfect but secure communication is such a chicken and egg problem that it is the best solution we have.

Client side hashing can be done over https

From your wording it seems you consider the question to be "client side hashing OR server side hashing over https" but I hope you can see that is a fallacy. You need https regardless of how you will authenticate your users. So if you accept you need https, then the question becomes "Why or when would it make sense to use client side hashing (over https) instead of server side hashing (over https)? In most cases it wouldn't. Client side hashing is more complex and not any more secure but it does have one advantage and that is the server has zero knowledge of the password which is not the case in server side hashing. In some scenarios you want you (the server) to be deniable. You can't lose a key you don't have, and you can't be forced to decrypt a customer's files if they never gave you the decryption key. So there are scenarios where it may be a mandatory requirement but it should be part of the project design not an arbitrary decision. If you have specific questions on how to handle zero knowledge scenarios it would be better done as a new question.

Gerald Davis
  • 2,250
  • 16
  • 17
13

bcrypt is not meant for this type of client-side hashing

A key property of bcrypt is that, when run two independent times with the same plaintext, most implementations will produce different hashes. This is due to the use of a salt, which is designed to make it difficult to see if two different users have the same password.

In contrast, login forms need to receive the same data every time. After all, how else would you verify that the password the user typed today is the same as what they typed yesterday?

So, you have an algorithm designed to produce different data each time, and you're feeding it into an application that needs to verify the data is the same each time. That's probably an indication that the algorithm is not being used as intended.

What you should ideally be doing is using bcrypt to hash passwords on the server. That's what bcrypt was designed for: protecting user passwords during times when the plaintext is not in memory, which is by far the most common state (e.g., DB dumps would reveal only the hashed password, not the plaintext, if implemented correctly).

I personally see some value in client-side hashing as well, because it does help protect against attackers who can sniff the traffic (or who have server access). However, I don't know of a way to make client-side hashing as secure as server-side bcrypt - which is why you should still be using server-side bcrypt.

If you must use bcrypt client-side, use a static salt

If you're GOING to use bcrypt for client-side hashing of a login form, and you want it to be a drop-in replacement for MD5, you need to use a static salt. Especially if you're passing it through SHA1, because that would mangle the bcrypt salt as well as the hashed data itself.

This does break several design assumptions of bcrypt (such as always using a random salt), of course.

I'd personally recommend using the username as a salt (so that it's difficult to tell whether two different users have the same password); however, I don't know of any research that's been done on salting in this context.

AES (or any symmetric cipher) is useless here, too

Keep in mind that AES is a symmetric algorithm, meaning that you need the same key to decrypt and to encrypt data.

What does this mean for you? Well, you've probably got your encryption key in the JavaScript source, which is served to any client that visits your login page. AES is a symmetric cipher, so your encryption key (which is identical to the decryption key) should be kept private. In other words, your private key is known to the world - it provides effectively no security at this point.

What you should be using instead is public key cryptography, such as PGP or HTTPS (technically, HTTPS uses a hybrid approach). Seriously, just use HTTPS, unless your organization refuses to let you have an SSL certificate, for some reason. Keep in mind that PGP won't really protect against active MITM attacks, but it would at least protect against eavesdropping.

Soron
  • 2,809
  • 1
  • 12
  • 19
  • Using a static salt on the client really removes all security implied by the client-side hashing. With a static salt, the hash result becomes the new "password" that grants access when sent to the server. By using a "static salt" you just spent a lot of computing cycles for nothing. Using the user name as salt is much better, but still implies collisions that help the attacker (i.e. the attacker may optimize things by precomputing hashes, e.g. as a rainbow table, for user name "admin", because most site using that software will have a user named "admin"). – Tom Leek Jul 09 '15 at 13:08
  • 1
    Using a combination of the user name _and_ the server name as salt is still better; you still have salt collisions when a user changes his password (the old and the new hash can still be attacked in parallel). The _really_ general method for doing client-side hashing is a two-step protocol where the client first sends the target user name, then gets the salt, computes the hash with that salt, and sends the result back -- and the server must still do one extra hashing (a fast one) so that what the client sends is not what the server stores. – Tom Leek Jul 09 '15 at 13:27
  • 6
    That kind of client-side hashing is called "server relief". There _is_ a lot of theory on that subject. I suggest beginning with [this answer](http://security.stackexchange.com/questions/211/how-to-securely-hash-passwords/31846#31846). – Tom Leek Jul 09 '15 at 13:28
  • 2
    Couldn't the server just send the salt previously stored for the user along with the form? Would that imply any security issue? – Dolda2000 Sep 06 '17 at 00:53
  • I am one of those stupid guy who see routinely queries going over HTTPS then being proxied (or reverse proxied) all over "local" network in plain text !!! So, HTTPS is not always "on". We are so stupid that we do not even trust our own infrastructure. In the end, it's simpler to have something like WSSE & HMAC from the beginning and not care about the possibility that the request is exposed in plain text because it has to go through a service which do not support HTTPS... Morale : don't insult people who know why they ask what seems so stupid. They have reasons. – programaths Sep 07 '17 at 19:23
9

The best solution is: don't.

If you're sending the passwords over HTTPS, hashing them provides no additional security.

If you're sending the passwords over plain HTTP, an attacker can grab the hashed password and use it to log in themselves; alternatively, they can tamper with the JavaScript to send the password to them before hashing. In either case, hashing the password provides no security.

Mark
  • 34,390
  • 9
  • 85
  • 134
5

Since you seem committed to implement this guideline, I'll directly answer your question. But understand that BCrypt and MD5 are vastly different. BCrypt deliberately does substantial work, while MD5 does considerably less, so you're going to need to deal with promises:

<!-- https://github.com/nevins-b/javascript-bcrypt -->
<script src='bCrypt.js'></script>
<script src='isaac.js'></script>
<script>
  // ... your code, then:
  var bcrypt = new bCrypt();
  bcrypt.hashpw('plain password', bcrypt.gensalt(), function (hashed) {
      var cipher = AES(SHA1(hashed));
      // now you have your "secure" hash
  });
</script>

I urge you to consider the sage advice in the other answers. The scenarios where this guideline adds value are rare, so ask yourself: Why invest double work in redundant infrastructure that adds no security and slows the user experience?

References:

  1. Transport encryption shields all secrets.

  2. Sending a hash is equivalent to sending a password, and generating a secure hash is necessarily slow.

  3. The brute-force resistence of bcrypt versus MD5 for password hashing? and hash algorithm performance test results.

bishop
  • 314
  • 2
  • 9