133

If on a login screen user submits a form with their username and password, the password is sent in plain text (even with POST, correct me if I am wrong).

What is the right way to protect the user and his password against the third party who might be eavesdropping on the communication data?

I am aware that HTTPS is a solution to the problem, but is there any way to ensure at least some level of security using the standard HTTP protocol (POST request)? (perhaps using javascript in some way)

What I was about was a page - that is a PHP-generated login page, which is of course sent to users in HTTP GET request as an HTML file. There is no (@Jeremy Powel) connection established between the server and the client so I can't create such a handshaking protocol. And I want the complete process to be transparent to the user - he wants to submit a password, not deal with cryptography.

Kornelije Petak
  • 9,412
  • 15
  • 68
  • 96
  • Good point about the persistent connection. – Jeremy Powell Oct 17 '09 at 18:47
  • 1
    You probably won't be able to accomplish this without the client using cryptography, but the user doesn't have to see such a process. He just enters his password and the code your PHP generates (javascript for example) handles it all for you. – Jeremy Powell Oct 17 '09 at 18:48
  • 19
    The problem you describe is the reason HTTPS was invented. If you send a secret down to the client to encrypt the password an eavesdropper will be able to sniff it and decrypt the password on the return trip. – jnoss Oct 17 '09 at 18:51
  • 1
    So S in your suggestion could be only password (or username+password combined in any way), as this is the only "secret" the user has. Am I correct? So the solution would be as folows: - Server provides the HTML page with a hidden form field R - The user enters the password, and before the password is sent, the javascript calculates H(R,S) and sends it to the server, perhaps even by using AJAX - The server calculates H(R,S) and compares it with received and sends a response to ajax request whether the authentification passed - The javascript redirects the browser to desired webpage – Kornelije Petak Oct 17 '09 at 18:56
  • But I'm guessing the last step is some kind of security issue. Anyway, I will look into HTTPS option as well. – Kornelije Petak Oct 17 '09 at 18:57
  • I really think HTTPS is a good idea. :) Don't use it for everything. Once you get that authentication token (cookie) you don't need SSL anymore, so you'll only need it in one small place. – Jeremy Powell Oct 17 '09 at 19:08
  • 2
    @jeremy powell - while what you describe is common practice, it is also vulnerable to an intermediary who can sniff the cookie from a header and impersonate the user by reusing the cookie. Man in the middle attacks are hard to secure against unless you are using HTTPS – jnoss Oct 17 '09 at 20:03
  • @electronherder: Agreed. – Jeremy Powell Oct 18 '09 at 05:06
  • 1
    For whoever gets to this question in the future: AFTER logging in, you also need to secure the session cookie. (So: using HTTPS is really so much easier.) – Arjan Nov 23 '10 at 19:24

9 Answers9

86

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.

fabpico
  • 2,628
  • 4
  • 26
  • 43
Jeremy Powell
  • 3,426
  • 2
  • 21
  • 29
  • 16
    ...and *"But I have to pay for an SSL certificate!!"* is not a valid complaint, since you can get them for $30 these days. Is your data really not worth 30 bucks to protect? – caf Oct 18 '09 at 02:08
  • 3
    What if the webhost you subscribed to does not support adding SSL certificates? – Calmarius Jul 22 '13 at 14:13
  • 119
    @Calmarius - then you move to a real webhost – BornToCode Oct 07 '13 at 19:57
  • 3
    @BornToCode This technically means you need to have a dedicated IP and you need to own the server hardware (or at least a VPS) to use HTTPS. Shared webhosts cannot do HTTPS, unless the entire server is protected with the host owner's certificate. – Calmarius Oct 07 '13 at 20:29
  • 7
    shared webhosts certainly can do https, using http://en.wikipedia.org/wiki/Server_Name_Indication – Brian Minton Apr 09 '14 at 12:27
  • 3
    @BRianMinton you're 100% right. I run a linode hosting multiple domains and only some of them have SSL, some running SSL on non-standard ports, etc. This could easily be done on a non-vps as well, since it's just a matter of throwing a few lines in to the apache config for the domain in question. –  Sep 23 '14 at 18:05
  • 4
    It's also worth nothing that letsencrypt.org is a non-profit organization with the goal of providing free SSL certificates to everyone. – Brian Minton Jan 29 '20 at 19:01
50

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:password@your.domain.com/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

nishanthshanmugham
  • 2,967
  • 1
  • 25
  • 29
FullStackForger
  • 1,060
  • 2
  • 12
  • 18
  • 13
    The problem with sending credentials (password) as GET parameters is that the user/password pair will probably end up in server logs which is not a good idea. It is best to send credentials in a POST. – zaph Apr 04 '16 at 13:02
  • Ahh... not at all. Screenshot you are seeing is modified to illustrate what is happening in a browser. The moment you hit enter browser will convert your url creating a `Authorization` header. Just give it a go. Logs will remind clean. And of course if you making a call from the server (if that is the scenario you are worrying about) you should generate header programmatically of course. – FullStackForger Apr 04 '16 at 23:37
  • You can not log what you can not see. `username:password@url` from the browser translates to: `url` + `Authorization` request header. As for GET queries... well like I said, use Authroziation header. It is better. – FullStackForger Apr 05 '16 at 11:47
  • I my current project I require JWT authorization for all the routes. To do so the token has to be obtained first by logging in first. `/auth/login` allows both POST and GET method. POST expected to have a payload with `login` and `password` and GET requires basic Authorization (header). When you login from either SPA or mobile app you will either call POST with payload or call GET with the header or as a pure **convenience** type `user:pass@url' into a browser address bar, which converts to a GET with header header. Logs are clean in all cases. – FullStackForger Apr 05 '16 at 12:00
  • Hope I made it clear @zaph. But regardless of what you find here please run some tests for yourself and take a look at links from my post, those should help. – FullStackForger Apr 05 '16 at 12:00
  • @zaph I apologise if my English wasn't clear enough. Also, I have read your comment twice. Did the same with mine :) and honestly I have never suggested to send sensitive data with as GET params. I totally agree with you on that one. In yesterday's comment I have described production setup referring to your concerns, hoping it may help to clarify few things. Hope it helps. – FullStackForger Apr 06 '16 at 16:23
  • @zaph Thanks for your input. I have updated my answer (both content and the image as well) – FullStackForger Apr 06 '16 at 16:56
  • I agree: "I have never suggested to send sensitive data with as GET params." But some developers do and I just felt your statements would benift from a clarification for that instance. I'm deleting most of my comments and tis on later, they are no longer relevant. – zaph Apr 06 '16 at 19:38
  • 2
    You don't address the point of the question: protecting sensitive data from man-in-the-middle eavesdropping. The focus is not to find an alternative and/or more standardized way to pass credentials, but protect them over an insecure channel. – Lord of the Goo May 16 '16 at 12:38
  • 4
    It should be emphasized that this solution only works if you are encrypting the connection with TLS/SSL already. base64 is not encryption. – Scot Nov 16 '17 at 00:15
  • This answer confused one of my developers. POSTing password and username is perfectly fine. Just make sure to use HTTPS. Practically every login solution POSTs credentials. – André C. Andersen May 19 '20 at 12:09
  • @Scot: I edited the answer to make it clear that base64 is not encryption. – nishanthshanmugham Jul 23 '21 at 19:04
14

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.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Jeremy Powell
  • 3,426
  • 2
  • 21
  • 29
12

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.

Calmarius
  • 18,570
  • 18
  • 110
  • 157
  • In this case is the password really protected? Couldn't someone sniff out what is sent and decrypt it using the public key and then just update the timestamp when they use it later? Am I missing something? – michaellindahl Mar 28 '14 at 21:23
  • @michaellindahl asymmetric encryption means that only the private key - which never leaves the server - can be used to decrypt things. Public keys can only be used to encrypt. – Dan Mar 13 '15 at 16:40
  • 2
    A computer between the browser and the server could change the public key on login page. – Antti Jan 23 '17 at 14:46
  • Thanks for sharing your methods, i found very interesting everthing. The timestamp added on sending creedentials is a good catch! One question about the thing about using extra session cookie whose value is a random string: is like a token for the next request only? – Victor Jun 28 '20 at 13:04
7

You can use SRP to use secure passwords over an insecure channel. The advantage is that even if an attacker sniffs the traffic, or compromises the server, they can't use the passwords on a different server. https://github.com/alax/jsrp is a javascript library that supports secure passwords over HTTP in the browser, or server side (via node).

Brian Minton
  • 3,377
  • 3
  • 35
  • 41
4

I would use a server-side and client-side Diffie-Hellman key exchange system with AJAX or multiple form submits(I recommend the former), although I don't see any good implementations thereof on the internet. Remember that a JS library can always be corrupted or changed by MITM. Local storage can be used to help combat this, to an extent.

nanofarad
  • 40,330
  • 4
  • 86
  • 117
1

HTTPS is so powerful because it uses asymmetric cryptography. This type of cryptography not only allows you to create an encrypted tunnel but you can verify that you are talking to the right person, and not a hacker.

Here is Java source code which uses the asymmetric cipher RSA (used by PGP) to communicate: http://www.hushmail.com/services/downloads/

rook
  • 66,304
  • 38
  • 162
  • 239
0

you can use ssl for your host there is free project for ssl like letsencrypt https://letsencrypt.org/

mk990
  • 103
  • 1
  • 1
  • 9
  • 2
    Check third sentence in question. (Also, always read other answers to make sure you're not adding a less-detailed duplicate.) – Nathan Tuggy Aug 26 '17 at 06:32
0

Using https sounds best option here (certificates are not that expensive nowadays). However if http is a requirement, you may use some encription - encript it on server side and decript in users browser (send key separately).

We have used that while implementing safevia.net - encription is done on clients (sender/receiver) sides, so users data are not available on network nor server layer.