Governments have large departments concerned with proving who you are. Most countries have national ID cards, and nearly all of them have Passport Offices.
We don't have anything as exciting. Just a database, with some stuff in it. So how can we prove a user is who they claim to be?
There are, fundamentally, three ways we can prove identity.
- Something you know.
- Something you have.
- Something you are.
Ideally, we want to use more than one, so-called "Multi-Factor Authentication" (MFA), and often "2 Factor Authentication" (2FA).
Let's go through each:
Biometrics involve examining some (hopefully unique) physical attribute and basing a decision upon it. These are very useful in some cases - mostly cases where the point of authentication is physically close to the user. As an example, biometric ePassport gates work pretty well, as do fingerprint scanners for login screens on laptops.
It becomes trickier when there's a physical distance, and in general terms, when the hardware collecting the biometric - the fingerprint scanner or whatever - isn't owned by the entity doing the authentication. After all, if I just had to send the right data, I could collect it once from your finger and replay it every time.
Similarly, there's the question of whether your finger is still attached to the rest of your hand - but let's not dwell on that...
Suffice to say that biometrics are all very good, but in almost every case they're not for us.
Smartcards and other hardware tokens are things you can physically carry on your person, which can be used to identify you. Examples of hardware tokens you're probably familiar with include bank cards, and Google Authenticator.
My bank card, for example, contains a cryptographic private key which I cannot extract, and can sign a challenge over either NFC (for contactless payments) or via a physical connection (for "Chip and Pin"). It also has a magnetic strip (for the United States, basically) which isn't nearly so clever.
The private key in a smartcard cannot be copied - any attempt to physically extract it would result in the card being destroyed. Obviously someone could just steal it - but I'd probably notice quite quickly, and for Chip And Pin at least, they'd need to know the PIN.
My SSH key is also on a smartcard - and I need to enter a PIN to use it, too.
Other devices exist that have a small screen with frequently changing numbers on it. These are generated from a secret key hidden inside the device in an equally unextractable way, and the server knows how to verify that the number generated comes from the device.
You can also consider client-side PKI certificates to be - sort of - Something You Have, even if they're just a file on disk. They can be copied, of course, but they can also be well-encrypted.
Google Authenticator doesn't work in quite the same way - it's almost trivial to extract the shared secret that the underlying technology, TOTP, uses, and copy it to multiple devices. The same secret also has to be stored on the server, so there's a risk an attacker could grab it from there, too - it turns out that Google Authenticator is not proof at all against a breach. But it's a lot better than nothing.
Passwords are probably the most familiar of authentication methods. They're also regarded as the weakest. People using passwords pick terrible ones, and people implementing password checking do it in appalling ways.
Rather than look at how to pick and handle passwords safely (short version: just use a password manager), I'll look at how to check passwords.
The venerable specification of OSI Directory authentication, X.509, covers both PKI and what it calls "Simple Authentication". In this, the user has a username, and knows a password. They send the password to the Directory Service Agent (ie, server). It compares it to its record (an attribute in the user's Directory Service Entry). If it matches, we're done. Hoorah!
Except that's as weak as it could possibly be. Let's consider.
For a start, if the server here is breached by an attacker, the attacker can grab all the passwords for all the users. That's clearly hideous, and we don't want to allow that. So the right solution here is to use a cryptographic hash-based function, to allow us to prove the password is right, without actually storing it.
If you're stuck, a good one here is PBKDF-2, which is, loosely, lots of repeated hashes, and can be based on any hash function. Use SHA-256 unless you know something I don't - which you may.
But, if an attacker can gain slightly longer-term access to your server, they can just hang about and the user will send them the passwords. While breaches done this way are in principle easier to detect, in practise people don't seem to notice huge databases being exported, so that may well be wishful thinking.
Ideally, then, even if TLS is being used, we want to assume an attacker might see the authentication exchange. The solution, then, is not to send the password itself, but use a challenge-response system to prove knowledge.
The trouble is, in order to verify most of these schemes, the server needs to know the password itself again (or a "plaintext equivalent" - ie, it has to have enough information to itself authenticate). This is a Bad Thing - an attacker can either try the passwords on other services, or simply use them to gain apparently legitimate access to the system they've just hacked. This is Not Good.
The IETF can help you here - SCRAM is a family of mechanisms designed around common building-blocks like PBKDF-2, HMAC, and so on. It has the useful property that it's (quite) hard to get passwords off the wire, and (quite) hard to get them from the database, too. In both cases, an attacker can brute-force the password well before the heat-death of the universe, but it's good enough for the vast majority of cases.
For really serious cases, there's SRP. Secure Remote Password uses cryptographic techniques to go from a simple password into an asymmetric key pair. The server only has the public key, and the user only has to know a password, so it's impressive stuff.
However you authenticate, you want to make sure that when you make a decision predicated on authentication, you can securely tie that back to the authentication.
If authentication can occur on a channel (like a TCP session, or a sequence of HTTP requests), and then the channel can be switched (session hijacking, etc) then you have a TOCTOU issue. This is a Bad Thing.
The solution is almost certainly some combination of TLS and a channel identifier - for serious security, look at Channel Binding. For Surevine's purposes, we run authenticated operations over a single TLS-secured WebSocket (using XMPP, fact-finders) where we can, but making sure your session cookies are secure, HTTPS-only, etc is another approach.
What you need to do depends on your threat model, as ever.
But, I'd say at a minimum, a modern application needs to have:
- No plaintext equivalents stored.
- No plaintext equivalents on the wire.
- TLS for all network traffic.
- No TOCTOU.
- Optional Google Authenticator / TOTP.
A simple application will be using a SCRAM-based solution over TLS, with a secure session cookie, and hopefully do TOTP.
Authentication is one of the essential parts of any application. It's hard to get right, and it's not only bad for you, but terrible for your users, if things go wrong.
The state of the art is a moving target, but there are existing, well-established solutions for each part. Constructing your authentication well can give both users and your application strong protection even in the event of a breach, without compromising your user experience.