Passwords are the prototypical example of authentication based on something you know. Here we examine some of the details of implementing password-based authentication.

Creating Passwords

Who creates the password?

Here are the top five examples of weak passwords chosen by users in 2012:

  1. password
  2. 123456
  3. 12345678
  4. abc123
  5. qwerty

Those are consistent with older password hacks. For example, in 2010, Gawker Media (parent of big blog sites), was hacked. Of 250,000 disclosed passwords, about 1% were "123456" and another 1% were "password".

All this raises the question: how can we characterize "strong" passwords? They need to be passwords that are hard for attackers to guess. It turns out we already have such a characterization from our study of applied crypto. Recall that the security level of an algorithm is the exponent of the maximum number of guesses required to break an algorithm by brute force attack. When we talked about encryption schemes, the guesses were to find the key, and we implicitly assumed that keys were chosen uniformly at random from the space of all keys. For example, a 128-bit key is from a space that requires 2^128 guesses to search exhaustively.

Using entropy to measure password strength. We can use the idea of the number of guesses required for brute force search for passwords. But passwords aren't bit strings; they're character strings. That makes the math a little more complicated. Suppose there are N characters to choose from, and the password is of length L. Then there are N^L possible passwords. We want to find the security level H of that space. That is, we want an H such that 2^H is equal to the number of possible passwords. (Why use the letter H? Because the concept we're describing is known in the field of Information Theory as Shannon entropy, for which the letter H is traditionally used. And from now on, we'll write "entropy" instead of "security level" when we're talking about passwords.) Let's solve for that H:

      N^L = 2^H
  log N^L = log 2^H
  L log N = H log 2
        H = L (log N / log 2)
        H = L log_2 N

So if passwords are chosen uniformly at random from the lower-case latin alphabet of 26 characters, the entropy of an 8 character password is 8 lg 26 ≈ 37.6 bits. That's very low compared to the minimum security level for keys! Is it enough? According to a 2006 NIST report, the minimum level is 14 bits, and 30 is comfortable. But that material assumes an online attack model, in which attackers interactively guess passwords. In an offline attack, in which attackers have direct access to the password database, a higher level of security is necessary.

The last paragraph began by assuming that passwords are chosen uniformly at random from the space of all passwords—for example, the password is just as likely to be "iZ8#j" as "12345". But humans just don't chose randomly. So the entropy of human-chosen passwords is effectively much less than it would be if the passwords were chosen by a machine. Suppose, e.g., that the average high-school graduate has a vocabulary of around 50,000 words [Nagy and Anderson; Pinker "The Language Instinct"]. What if this person chooses an English word as password? There will be lg 50k ≈ 15.6 bits of entropy. That's low! And it assumes that users choosing randomly over their entire vocabulary, which isn't likely either.

The aforementioned NIST report uses the following heuristic for the entropy of user-selected passwords drawn from the full keyboard:

Other heuristics have been proposed, summarized in Schneider and Bishop. "Simple transformations" above could include deleting vowels, capitalizing some letters, adding suffixes/ prefixes, replacing letters with look-alike numbers, leet speak. And more, so read those sources. There are software libraries for these heuristics, and you are welcome to use them if you can explain what they do when asked.

According to Bruce Schneier (2007), quoting Eric Thompson of AccessData,

  1. The typical password comprises a root followed by an appendage.
  2. The root is typically pronounceable, though not necessarily a dictionary word
  3. The appendage is usually a suffix (90%) or prefix (10%).
  4. Most users have a 7–9 character root plus a shorter common appendage.
  5. A dictionary of 1,000 roots plus 100 suffixes (= 100k passwords) cracks about 24% of all passwords.
  6. More sophisticated dictionaries (including initial/final uppercase, and leet speak substitutions) crack about 60% of passwords within 2–4 weeks.
  7. Given biographical data (zip code, names, etc.) and other passwords of a user, the success rate goes up a little, and the time goes down to days or hours.

Storing Passwords

After creating a password, both the human and system must store it.

Humans don't have the storage capacity to remember lots of strong passwords. So they naturally do a mixture of

The classical example of bad recording is writing down passwords on post-it notes next to monitors. But there are some better ways of managing recording. Humans are pretty good at securing pieces of paper, called cash, in their wallet. So recording passwords in your wallet might help. (Though loss of your wallet now has even greater impact than before.) Various electronic password wallet applications are also available, and can be very convenient to use—even making it easy to use random passwords, one per identity. But those apps (and their data, if any) now become targets.

Systems store passwords in a file (or a database). If that file is sufficiently secured with appropriate access control mechanisms, then storing plaintext passwords could in principle be okay. But real-world experience teaches us that password files get leaked. So for Defense in Depth, don't store plaintext passwords!

Techniques for Protecting Stored Passwords

How can we protect passwords such that, even if the password file is disclosed, attackers don't learn users' passwords? We want a function f such that, if we store f(pass) for a password pass, then it's hard for attackers to invert f(pass) and find pass. When we studied applied crypto, we called that property one way, and we saw that cryptographic hash functions provide that property. (Encryption does too, but encryption requires a key, whereas hashes do not.)

Hashed Passwords

What to store: uid, H(pass)

How to authenticate:

  1. Hu → L: uid, pass
  2. L: if H(pass) is stored as uid's password, then uid is authenticated

In the scheme above, uid is an identifier for the user, H is a cryptographic hash function, and pass is a password. During authentication, a human Hu is interacting with a local machine L—that is, the human is communicating directly with the machine (e.g., by a keyboard), not over a network.

With this scheme, if the attacker obtains the password file, he doesn't immediately learn the plaintext passwords. But it's not a perfect solution, because, besides inverting the hash function, there's another way to learn passwords. An attacker can construct a dictionary mapping plaintext passwords from their hashes:

H(pass1), pass1
H(pass2), pass2
H(pass3), pass3

Given such a dictionary, and given a hashed-password file, the attacker can simply look up the hashed passwords and find the plaintext passwords. So the question becomes, how hard is it to construct a dictionary?

It turns out that, when users choose weak passwords, it's not all that hard. (Another reason to use random passwords!) Special data structures called rainbow tables can greatly reduce the space requirements. It's even possible to buy or download free, precomputed tables [].

What countermeasures can defend against dictionary attacks?

Idea 1: Slow down. One reason that it's easy to construct dictionaries is that cryptographic hash functions are designed to be quick to compute. A hash function that was slow to compute would cause table construction to take more time, making it more difficult for attackers to succeed. There's a function called "scrypt" that is designed in just this way. Another means to slow down computation is to iterate the hash function, perhaps a thousand times:

Iterated password hash:

z1 = H(pass)
z2 = H(pass, z1)
z1000 = H(pass, z999)
output z1 XOR z2 XOR ... XOR z1000

(This algorithm is almost the same as PBKDF2 from RFC 2898.)

Another term for this technique is key stretching. The number of times to iterate can be a parameter. Over the lifetime of a system, the parameter can be adjusted upwards to account for increasing attacker computational power. When PKBDF2 was proposed in 2000, a minimum of 1,000 iterations was suggested. As of 2013, 10,000 is a minimum, and 20,000–40,000 is not unreasonable. []

Idea 2: Add salt. Dictionary attacks succeed because the password space is so small. If we increased the size of that space we could make the table size infeasibly big to compute or to store. The trick is to do so without making users choose longer passwords. We can do that by introducing a nonce, which is commonly called salt. Every user is assigned their own unique salt.

Hashed Salted Passwords

What to store: uid, salt, H(pass, salt)

How to authenticate:

  1. Hu → L: uid, pass
  2. L: lookup salt for uid; if H(pass, salt) is stored as uid's password, then uid is authenticated

Note that salt doesn't need to be unpredictable, just unique. But to prevent password cracks on one system from being effective on another system, salt does need to be unique across systems. Generating the salt randomly is the easiest way to achieve that.

To combine salt with iterated hashing, just salt the first hash: z1 = H(pass, salt); z2 = H(pass, z1); etc.

If all systems would just use hashed, iterated, salted passwords, the world would be a better place.