19

I am building a web site that provides user login. For that, I am currently researching good strategies for dealing with authentication.

How I'm doing it right now

My current concept is modeled after what seems to be the common consensus right now. Passwords are salted with 64 bytes from /dev/urandom and then hashed with 100 rounds of SHA-512. After every round, the original password is concatenated to the result and then fed into the next round. When a user wants to log in, they send their credentials to the server, where the described procedure is then repeated (using the same salt, obviously) and compared to the hash in the database.

This strategy seems adequately secure to me (please correct me if I'm wrong, it is basically just the result of reading a lot of online guides and watching YouTube videos). However, I think it has a major flaw, which is the client having to send the password in plain text to the server. Yes, I naturally use HTTPS, but still, if the connection was somehow compromised for whatever reason, so is the password.

The alternative concept

So I thought of an entirely different approach: using PGP keys. When a user signs up, they generate a PGP key pair, encrypt the private key using a password of their choice and send it to the server. When they want to log in again, the server generates a string of random characters and encrypts it using the public key. The encrypted random string and key pair are then sent to the client, who will need to decrypt it again to prove they have the private key's password.

This method would prevent the password from ever being transmitted over the network and even allow for cool stuff like end-to-end encrypted chat between users. The only drawback I could find is the server having to give out encrypted private keys to basically anyone who requests them, making brute-force attacks way easier. I could mitigate that by running a computationally expensive key expansion algorithm on the client side and use the result of that for encrypting the private key.

But I still don't really trust the whole thing, and so I would love to hear your feedback on whether this is a good idea or if I should just stick with how I'm doing it right now.

EDIT:

Based on some of the answers, I think my question is a little misleading. My requirement is that the only thing users ever need to provide for successful authentication is their username/password and nothing else, regardless of what device they are using or whether they were logged in on that device before.

  • 4
    This requires the client to store the key somehow on the local system (can be inside the browsers local storage) which kind of binds the key to the device, i.e. it is far less flexible than a password. If you find this limitation acceptable or even useful then why not use client certificates then which essentially already do what you are trying to reimplement with PGP? – Steffen Ullrich Jan 8 at 19:39
  • 8
    Your current hashing method is quite close to PBKDF2. I'd suggest using an actual PBKDF2 implementation from a mature library or, if you can, use more robust key stretching algorithms, such as bcrypt, scrypt, or Argon2. If you're dead-set on SHA-512, you can keep using it with PBKDF2, though most libraries will default to SHA-256 and might not let you configure that option. – Ghedipunk Jan 8 at 22:09
  • 15
    Um, logon via TLS certificates is a thing... No need to involve GPG/PGP, whose "web of trust" model contributes nothing here. – Marcus Müller Jan 8 at 23:36
  • 9
    Inventing any new technique on your own is not a good idea. – chrylis -cautiouslyoptimistic- Jan 9 at 9:10
  • 2
    Just think about the following: The user will enter his password into a website, the website is delivered via https, if https is compromised the attacker can just manipulate the website and directly get the password. As soon as the user enters the password, you have to trust your https-connection a hundred percent, so transmitting it is not really any more risky than entering it into the password-field. – Falco Jan 9 at 13:42
68

You should never try to secure a "real" web application with a scheme that you invented on your own. As such we shouldn't discuss practicalities on how you would actually implement or use such a method.

Will it work?

Your scheme does not send a password over the wire. What immediately jumps out is that you send the private key to the server, which never does anything with it, for no reason. Yes, it is supposedly still protected by the password, but why?

As others have pointed out, the private key is supposed to remain with the owner.

Even if you consider the private key's password encryption, there is the following problems (at least):

  • Handing out the private key in your scheme is essentially the same as making the password hash public: Anyone can now do an offline dictionary attack against the key's password; and nobody can stop that.
  • Even worse, they can get the key for a given login, meaning that they can check if a user exists and then attack the password for the user name.

With a small modification, your scheme would be a very simple form of public key authentication:

  • The client creates a key pair, and sends the public key to the server
  • To log in, the server sends out a random string.
  • The client signs the string and sends it back to the server.
  • The server verifies the signature to make sure the client has the private key

Something like this is actually already possible in https using client certificates. The same thing is used for ssh.

The Challenge-Response mechanism mentioned by ThoriumBR is another way of authenticating, although the implementation described there assumes that the server stores the password in cleartext -- which is much more dangerous than sending it over an encrypted connection.

Will it be "better"?

No. The scenario that you want to defend against is supposedly that someone gets inside the encrypted https connection. If you assume that this is the case, they would not only be able to read the password but also steal your session cookie and all the data that is shown in your session. In that case, the attacker has already won, regardless of whether they read the password or not.

You could of course roll your own encryption and authentication, but the question is why you'd think that this will be better than the https connection?

The realistic scenario isn't that someone breaks the https connection, it is that someone breaks into the server's database and tries to "recover" the passwords in an offline attack. This is what most authentication schemes try to defend against.

The danger with passwords is not that they are interceptable, but that they can be guessed.

What should you do in real life?

For a "real" web application, use an established framework and a well-known authentication library. Don't write it yourself, because it is easy to get wrong and very hard to get right.

Don't use SHA-anything for hashing the passwords, use a dedicated password-hashing function such as bcrypt or PBKDF2 to make attacks more difficult (see this answer also). You can also use or add some TOTP mechanism for your app, which is better than just a password.

Addendum

You added the following after I wrote my answer:

My requirement is that the only thing users ever need to provide for successful authentication is their username/password and nothing else, regardless of what device they are using or whether they were logged in on that device before.

This explains why you want to send the private key back to the server, although it doesn't change the reason why it is a bad idea.

You could still use a challenge-response mechanism, like ThoriumBR suggested. There are even advanced challenge-repsonse mechanisms such as SRP which use clever math to avoid storing the password on the server.

However, all those mechanisms, and also client-side hashing (see this answer), have one thing in common:

They need to execute code on the client side.

In a web app, this can only be Javascript sent by the server. Implementing the client side is not the problem (there is even a OpenPGP library), but since you worry about the security of the https connection, you have another problem:

Any man in the middle could therefore simply inject malicious client side code to steal the password, rendering your whole clever mechanism obsolete.

| improve this answer | |
  • There is TLS-SRP which don't depend on Javascript, but major browsers don't support it. There are some issues with SRP in general: lack of a PBKDF (in some implementations), the lack of weak password checking and, the major problem, the user interface would be either ugly (handled by the browser, like Basic HTTP Auth) or unsafe (handled by the website, which allows spoofing). WebAuthn is getting more traction. – Gustavo Rodrigues Jan 10 at 13:16
  • 1
    Nit: You have to specify a digest function to PBKDF2 (which basically runs it in a loop). I absolutely would recommend SHA-n (n > 1) for the digest function. – Martin Bonner supports Monica Jan 10 at 14:02
  • I clearly see your point, so I decided to discard any plans of some fancy homebrew authentication and just moved to bcrypt, which conveniently already has a complete implementation on npm. Do you think a cost factor of 12 (which takes about 200ms on my machine) is sufficient? Thank you for the answer by the way. – Sandtler Jan 11 at 21:23
  • @Sandtler I would recommend the OWASP Password storage cheat sheet - it currently recommends a work factor of 12 (and adding 1 every 18 months). – averell Jan 11 at 21:56
11

As the others said, private keys aren't shared. full stop. Otherwise, they are called public keys.

But: Cryptographically signing on is pretty standard. HTTPS supports that:

Authentication using HTTPS client certificates; and all modern browsers have the built-in means to generate the necessary keys when asked to.

Your problem has already been solved; the user generates a key pair, gives you its public key, keeps the private key, and upon connection, the server asks the client to prove they own the private key (essentially, by letting them sign something).

Done!

Other cryptographically established methods include at least one method I'm aware of:

Single-sign on via Kerberos ticket – this is extremely wide-spread in intranets that use Active Directory. Again, modern browsers (at least Edge, Internet Explorer, and Firefox) support this out of the box – on both windows and Linux (haven't tried on OS X or FreeBSD)

| improve this answer | |
  • Firefox only does Kerberos after the site has manually been added to a list of sites that you trust for Kerberos (or if your intranet has a deployment tool that configures this for you) for privacy reasons – Erik A Jan 9 at 11:46
  • @ErikA that is not generally correct, it seems. I can use kerberos tickets on all my linux machines for sign on in my university, freshly after I set them up. No external deployment tool necessary. Might depend on the local DNS configuration (i.e. local domain allowed by default, maybe?). – Marcus Müller Jan 9 at 13:43
  • That's odd, it's documented that Firefox rejects all SPNEGO messages by default, which is used to negotiate Kerberos authentication, and afaik there's no exception for the local network. Afaik, Kerberos requires SPNEGO to work over HTTP(S). And for my personal experience, I certainly can't use Kerberos on a blank Firefox install and must configure it as outlined in that document. – Erik A Jan 9 at 14:07
  • For a quick test, I flattened my firefox profile here, and opened the login page, clicked on "Windows Logon" (having run kinit user@DOMAIN from my fedora's shell, good naming, guys), and it just worked<sup>TM</sup>. – Marcus Müller Jan 9 at 14:50
  • @ErikA: The reason is Kerberos forwarding attacks are real things. samba used to operate exclusively in that mode a long time ago. – Joshua Jan 9 at 21:33
9

While I like the idea of PGP for login, this is not the way it's supposed to work. The private key should remain on the client side, always, and never be shared with anyone. This setup encourages bad practices and provide incorrect education about PGP.

What you could do is, have users upload their public key to the server, and then you present them a challenge (basically, an encrypted string) upon login, that requires the private key and of course the associated passphrase..

The problem is that few people know PGP or have a serious inclination to use it. If you can do it, go for it.

| improve this answer | |
  • 6
    "While I like the idea of PGP for login".. why? As far as I can see there's nothing in PGP that's useful for this scenario over just using client certificates for authentication - which is an established standard. – Voo Jan 9 at 12:27
  • isn't sending a public key or any kind of key exchange is vulnerable to man in the middle attacks? which is the main concern of the OP in the first place to bypass his current form of key exchange for another kind! This is addressed in Marcus Muller's answer. – Unicornist Jan 10 at 21:05
5

As Anonymous said, the private key must always be private. Sending the private key to any entity negates any security provided by your login.

Basically, what you are designing is called Challenge Response Authentication, and can be done even without a keypair:

  • One party (the server) generates a random token and sends it to the other

  • The other (the client) concatenates his own password with the token, hashes it, and sends back

  • The server knows both the token and the password, concatenate those values, hash, and compare with what the client sent

If you want to use a keypair, you can use the keypair to sign:

  1. Generate a random value R1 and send to the user

  2. User receives R1, and signs it using its private key

  3. Server checks the signature against user's public key

This is technically easy to do, but I doubt most users will have the necessary know-how to generate a keypair, and use the keypair to sign things.

| improve this answer | |
  • 6
    I think your first example is flawed - it requires that the server store the user's password in a form that's useful to an attacker. – Toby Speight Jan 10 at 9:29
  • I agree with Toby, you had the fact that the server knows the password! why?! – Unicornist Jan 10 at 21:08
5

An alternative approach that implements the same concepts is SRP. Ignoring the math in the protocol, the idea is that when a user signs up the client generates a password verifier which is sufficient to verify credentials, but not to act as them and sends it to the server to store.

At login time the parties then exchange messages based on random values that eventually leads to the server using the stored verifier to ensure that the client has the correct password. The messages that the client send to the server are one-way derived from the password and the random value so the password is never sent on the wire. The random message from the server ensures that the messages from the client are valid only for the current login attempt (prevents MITM from being able to replay login attempts if HTTPS fails).

While the protocol is daunting to implement, there are out of the box implementations out there, e.g. AWS Cognito and its client JS SDK, so it is practical to implement in any web app.

| improve this answer | |
4

While the scheme described in the question has a significant problem in enabling offline bruteforce attacks against the password, it is possible to redesign it without this vulnerability.

For example, one can generate private key directly from password. That avoids the need of storing the private keys at all - basically the password becomes the private key.

However in a practical web application implementation, you would have to do the cryptography in client-side javascript. And if HTTPS as transport is broken, it is quite likely also to enable compromising that piece of javascript, giving out the password in plaintext.

Such a scheme correctly implemented would be slightly more secure than current standard practice of plaintext over https + hashing on the server side. But without extensive review, it is easy to make subtle but critical mistakes in cryptography code, and effort would be better spent securing other parts of the application.

| improve this answer | |
  • This seems to be about exactly what I was looking for, thank you. I'll read some articles on how PBKDF2 works in detail and, if it fits my needs, see if there is a mature framework that implements this authentication mechanism as complete as possible in order to keep my own security-critical code to a bare minimum. – Sandtler Jan 9 at 15:34
4

This already exists.

OpenPGP has "authentication" as a possible key usage, and a PGP key can be used to authenticate an SSH session if gpg-agent is used in ssh-agent mode. I use that daily as a single-sign-on solution with a smartcard, but there is no reason why this wouldn't work with a regular key.

There are also various plugins that allow signing messages during a TLS handshake using gpg-agent, allowing users to use their PGP key for SSL client certificate authentication.

As the key never leaves the user's device, it isn't even necessary to generate a new key, users can just enroll with their existing key.

You need enough of a PKI infrastructure on the server side to decide which keys are valid for which users. Most services (e.g. gitlab, github and launchpad) allow you to upload your public key into a form in the user profile editor.

| improve this answer | |
  • You're examples of gitlab and github is for applications that actually use the ssh itself via their clients (git, etc) for the authentication. the OP is not considering this as an option, instead looking for a way to use PGP for web based authentication. – Unicornist Jan 10 at 21:12
  • ... which you already cited that is used inside TLS handshake process itself. So is it correct to consider that sending a plain text pass over HTTPS is already using a kind of PGP authentication mechanism in itself? – Unicornist Jan 10 at 21:13
  • @Unicornist, no, only when the certificate from the TLS session is used as part of the authentication -- which means connecting the browser to gpg-agent. Gitlab/GitHub are examples of how you would enroll users. – Simon Richter Jan 11 at 1:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.