Don't let password recovery keep you from protecting your users

Posted by Tom Moertel Fri, 09 Feb 2007 20:36:00 GMT

In 2006’s most-read article on my blog, Never store passwords in a database!, I urged web programmers, unsurprisingly, not to store passwords in their user databases. I tried to persuade them to salt and hash the passwords instead: store the salts and hashes in the database and throw the passwords away. The article, posted shortly after the Reddit blog announced the theft of its unprotected user database, generated buckets of comments. Reading over them today, I noticed something that I had missed earlier.

It seems that a decent slice of programmers think that switching to a salted-and-hashed password scheme implies giving up the ability to assist users who have forgotten their passwords. If the passwords are irretrievably hashed away, the programmers reason, there’s no way to recover forgotten passwords and email them to stranded users. Hence those users are screwed.

And that wrinkle, it might seem, is a good reason not to switch to a salted-and-hashed password scheme.

But that wrinkle turns out to be imaginary. Not being able to recover an account’s password does not mean that you can’t recover the account itself. The password, after all, is not the thing of value; the account is. And, as we shall see, we can recover an account without knowing its password.

Recall that the primary benefit of using a hash is that it is a one-way operation. Once you salt and hash a password, there is no practical way to retrieve it. That’s what protects it from would-be attackers. But that also means you can’t get at it, either. Thus sending password reminders to people who have forgotten their passwords is no longer an option.

How, then, can you help your stranded users? One method is to send them account-recovery tokens, which you can think of as one-time, special-purpose passwords. (This method is suitable only if you require no stronger authentication than knowing that your site’s users own the email addresses they claim to own. This is the case for most “low security” sites such as Slashdot, Reddit, and Digg, as well as most blogging systems.)

Here’s how it works. Say Joe has lost his password and can’t log in to your site. He clicks that button that says “I’ve lost my password. Help me!” Now what?

Here’s what you do:

  1. Generate a big, random, unique token and stuff it into Joe’s account record in the database. Stuff the current date and time in there, too.
  2. Send an email to Joe, but instead of enclosing his password (which you can’t recover), tell Joe to click on the enclosed account-recovery link, which includes the random token: http://example.com/recover-account?token=pCIqq1unxntVqc8XtCXg.
  3. Joe receives the email and clicks on the link, which sends his token to your site.
  4. Look up the token in the user database. Is it there?
    1. No? Render a screen that says, “Sorry, bub, that token is no longer valid.” Stop.
    2. Yes? Excellent. Grab the user record associated with the token. (It will, of course, be Joe’s record.)
  5. Is the date and time stamp on that record more than a few hours old?
    1. Yes? Render that screen that says, “Sorry, bub, that token is no longer valid.” Stop.
    2. No? Congratulations. Joe has effectively authenticated himself via his email address.
  6. Render a confirmation screen that explains the following to Joe:
    1. His account password is going to be reset to the following random string: ocZodbew. (Generate a new random string each time.)
    2. If he likes the password, great. If not, he can use the change-password feature immediately after the password is reset.
    3. If he understands the above and wants to continue, he should confirm by clicking the big “Reset My Account Password” button.
  7. Joe clicks the button.
  8. You, in response, do the following:
    1. Delete the recovery token from Joe’s user record in the database. (This prevents somebody from using the old token to steal his account, should, for example, Joe’s email get stolen.)
    2. Replace Joe’s old password with the new, randomly generated password from above. (You will, of course, use the salted-and-hashed method and not store the new password itself.)
    3. Log Joe in.
    4. Render a screen saying, “Joe, please don’t forget that your new password is ocZodbew. If you would like to change it, just visit Change My Password in your account preferences [provide a link]. Otherwise, you’re logged in and ready to go. Enjoy the site!”
  9. And you’re done.

The code required to make it happen is shorter than the explanation above. It’s one of those easier-done-than-said things.

So, if concerns about account recovery have been holding you back from protecting your users’ passwords, you need hold back no longer. It’s time to “do” your due diligence.

Update 2007-09-10: I made clear that the account-recovery method I describe above is suitable only for low-security sites where a valid email address is sufficient to authenticate users.

Posted in ,
Tags , , , , , ,
13 comments
no trackbacks
Reddit Delicious

Never store passwords in a database!

Posted by Tom Moertel Fri, 15 Dec 2006 18:25:00 GMT

Recently, the folks behind Reddit.com confessed that a backup copy of their database had been stolen. Later, spez, one of the Reddit developers, confirmed that the database contained password information for Reddit’s users, and that the information was stored as plain, unprotected text. In other words, once the thief had the database, he had everyone’s passwords as well.

Had the folks at Reddit salted and hashed the passwords, the thief would now be in a very different situation. Instead of holding all the keys to the kingdom, he would face the prospect of a potentially expensive search for each and every user’s password he wanted to extract from the database. The expense of the search would likely have dissuaded him from making the attempt in earnest, given how little exploitable value a Reddit account represents. In short, the passwords would have been secure, even though the database had fallen into the thief’s hands.

Why, then, didn’t Reddit’s programmers salt and hash the passwords before storing them in their database? Because, according to the earlier post by spez, they wanted to be able to send forgotten passwords to users via email. It was a design decision: they weighed the risks of having plain-as-day passwords in the database against the convenience of being able to email users their forgotten passwords and decided that, in the balance, convenience carried more weight. It’s a decision they now regret. (It’s a doubly unfortunate decision because you don’t need to store passwords in your user database in order to offer convenient account recovery.)

The reason I’m writing about this event isn’t to kick the good folks at Reddit while they’re down. Rather, I’m trying to make a point:

If you are storing passwords in a database, you are almost certainly making a mistake.

The guys at Reddit are known for being smart. They thought they had a good reason for storing passwords in their database. They were wrong. If smart programmers can make this mistake, lots of programmers can. Do you think you have a good reason for storing passwords in your database? If so, you’re probably wrong, too.

How can I be so sure? Because, when it comes to web-app authentication, cutting corners doesn’t buy you anything. It doesn’t save you coding time. It doesn’t give your users a better experience. All it does is weaken the security of your web application, needlessly putting your users, your employer, and yourself at risk.

So please let me take this opportunity to ask if you know of (or perhaps work on) any software systems that store passwords as plain, unprotected text in a database. If so, fix your software now:

  • Salt and hash each and every password (use an expensive hashing function such as bcrypt that was designed for password applications)
  • Store the salt and hash – not the password – in your database.
  • Throw the password itself away.

You’ll be glad you did.

Update: Minor edits for clarity.

Update 2007-02-13: Salting and hashing does not get in the way of account recovery. You do not need to email users their forgotten passwords: there are other account-recovery options that are just as convenient but much more secure. See Don’t let password recovery keep you from protecting your users for more.

Update 2007-10-03: Revised text slightly to emphasize that there is no benefit to be had by implementing a weak password system, and therefore there is no reason not to implement a secure system. Pointed more directly to bcrypt, too.

Posted in ,
Tags , , , ,
51 comments
no trackbacks
Reddit Delicious