How to send password securely over HTTP?

If your webhost allows it, or you will need to deal with sensitive data, then use HTTPS, period. (It's often required by the law afaik).

Otherwise if you want to do something over HTTP. I would do something like this.

  1. The server embeds its public key into the login page.
  2. The client populates the login form and clicks submit.
  3. An AJAX request gets the current timestamp from the server.
  4. Client side script concatenates the credentials, the timestamp and a salt (hashed from analog data eg. mouse movements, key press events), encrypts it using the public key.
  5. Submits the resulting hash.
  6. Server decrypts the hash
  7. Checks if the timestamp is recent enough (allows a short 5-10 second window only). Rejects the login if the timestamp is too old.
  8. Stores the hash for 20 seconds. Rejects the same hash for login during this interval.
  9. Authenticates the user.

So this way the password is protected and the same authentication hash cannot be replayed.

About the security of the session token. That's a bit harder. But it's possible to make reusing a stolen session token a bit harder.

  1. The server sets an extra session cookie which contains a random string.
  2. The browser sends back this cookie on the next request.
  3. The server checks the value in the cookie, if it's different then it destroys the session, otherwise all is okay.
  4. The server sets the cookie again with different text.

So if the session token got stolen, and a request is sent up by someone else, then on the original user's next request the session will be destroyed. So if the user actively browsing the site, clicking on links often, then the thief won't go far with the stolen token. This scheme can be fortified by requiring another authentication for the sensitive operations (like account deletion).

EDIT: Please note this doesn't prevent MITM attacks if the attacker sets up their own page with a different public key and proxies requests to the server. To protect against this the public key must be pinned in the browser's local storage or within the app to detect these kind of tricks.

About the implementation: RSA is probably to most known algorithm, but it's quite slow for long keys. I don't know how fast a PHP or Javascript implementation of would be. But probably there are a faster algorithms.


You can use a challenge response scheme. Say the client and server both know a secret S. Then the server can be sure that the client knows the password (without giving it away) by:

  1. Server sends a random number, R, to client.
  2. Client sends H(R,S) back to the server (where H is a cryptographic hash function, like SHA-256)
  3. Server computes H(R,S) and compares it to the client's response. If they match, the server knows the client knows the password.

Edit:

There is an issue here with the freshness of R and the fact that HTTP is stateless. This can be handled by having the server create a secret, call it Q, that only the server knows. Then the protocol goes like this:

  1. Server generates random number R. It then sends to the client H(R,Q) (which cannot be forged by the client).
  2. Client sends R, H(R,Q), and computes H(R,S) and sends all of it back to the server (where H is a cryptographic hash function, like SHA-256)
  3. Server computes H(R,S) and compares it to the client's response. Then it takes R and computes (again) H(R,Q). If the client's version of H(R,Q) and H(R,S) match the server's re-computation, the server deems the client authenticated.

To note, since H(R,Q) cannot be forged by the client, H(R,Q) acts as a cookie (and could therefore be implemented actually as a cookie).

Another Edit:

The previous edit to the protocol is incorrect as anyone who has observed H(R,Q) seems to be able to replay it with the correct hash. The server has to remember which R's are no longer fresh. I'm CW'ing this answer so you guys can edit away at this and work out something good.


Using HTTP with SSL will make your life much easier and you can rest at ease. Very smart people (smarter than me at least!) have scrutinized this method of confidential communication for years.


Secure authentication is a broad topic. In a nutshell, as @jeremy-powell mentioned, always favour sending credentials over HTTPS instead of HTTP. It will take away a lot of security related headaches.

TSL/SSL certificates are pretty cheap these days. In fact if you don't want to spend money at all there is a free letsencrypt.org - automated Certificate Authority.

You can go one step further and use caddyserver.com which calls letsencrypt in the background.

Now, once we got HTTPS out of the way...

You shouldn't send login and password via POST payload or GET parameters. Use an Authorization header (Basic access authentication scheme) instead, which is constructed as follows:

  • The username and password are combined into a string separated by a colon, e.g.: username:password
  • The resulting string is encoded using the RFC2045-MIME variant of Base64, except not limited to 76 char/line.
  • The authorization method and a space i.e. "Basic " is then put before the encoded string.

source: Wikipedia: Authorization header

It might seem a bit complicated, but it is not. There are plenty good libraries out there that will provide this functionality for you out of the box.

There are a few good reasons you should use an Authorization header

  1. It is a standard
  2. It is simple (after you learn how to use them)
  3. It will allow you to login at the URL level, like this: https://user:[email protected]/login (Chrome, for example will automatically convert it into Authorization header)

IMPORTANT:

  • As pointed out by @zaph in his comment below, sending sensitive info as GET query is not good idea as it will most likely end up in server logs.
  • The Authorization header value is traditionally a base64-encoded username/password. Base64 is not encryption. The original value can be obtained by an on-path attacked using a simple base64-decode.

enter image description here