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

Comments

  1. Joakim Recht said about 4 hours later:

    Even better: Generate a pair of tokens: A short, readable one which is displayed on screen to the user, and a longer random one which is mailed.

    The link in the mail then points to a page where the user must enter the first token, and the system checks if the long and short tokens exist and match. This way you’re completely sure nobody can hijack the account.

    //Joakim

  2. Rob Kinyon said about 5 hours later:

    Why even generate a password for them? If they’re authenticated, just drop them into “Change Password” from the getgo.

  3. Tom Moertel said about 6 hours later:

    Joakim, I don’t see how the screen token increases resistance to hijacking. If the user’s email is compromised, the screen token will not help because the attacker can obtain a valid screen token simply by making an account-recovery request on behalf of the target account. And if the user’s email is not compromised, what does the screen token add? Am I missing something?

    Rob, to answer your question, the method you suggest is fine. I prefer to auto-generate a random password, however, to lower the user’s cost of “picking” a good password or, to look at it another way, to increase the cost of picking a bad password.

    Thanks for your comments.

    Cheers,
    Tom

  4. Luke Plant said 2 days later:

    I use something similar to this, but it doesn’t require touching the database in anyway until the confirmation link is clicked. Instead, I do something like (from memory):

    1. generate a string that is timestamp+delimiter+username+delimiter+Hash(mysecret+timestamp+username)
    2. ‘scramble’ this and send in e-mail as part of a URL to click on.
    3. in handler for this link:
      1. unscramble the URL parameter
      2. parse out the username, timestamp and hash
      3. check the hash matches (so people can’t generate their own strings for resetting arbitrary passwords)
      4. if so, generate, set and display to the user a new password.
  5. Kurt Hutchinson said 2 days later:

    The thing that has always made me uncomfortable about password recovery emails is that the more email servers the message passes through, the higher the chance that one of those servers is compromised (or maybe an email admin is bored, etc) and some unintended eyes could intercept the email. Is there a straightforward way to protect against that? The only thing I can think of is allowing the user to record a public key in their profile and use that to encrypt the message. For tech-savvy users I guess that would be alright, but I’m not sure it’s reasonable for most.

  6. Tom Moertel said 2 days later:

    Luke, the downside to method you propose is that you must keep mysecret secret, even when other compromises have occurred. If the secret becomes known, a database compromise becomes a complete account compromise: an attacker can “recover” any and all accounts by generating recovery strings. Unfortunately, many compromises that reveal the database (e.g., theft of sources, laptop, or back-up tapes) are also likely to reveal persistent secrets.

    Against the particular implementation spelled out in your comment, an attacker doesn’t even need the database if he knows mysecret: all he needs is a username to successfully attack an account. And usernames are pretty easy to obtain.

    Cheers. —Tom

  7. Luke Plant said 4 days later:

    Tom

    Thanks for pointing that out, I’ll bear it mind. (With the nature of system that this is in place for, the risk posed by this attack is not at all critical. And, unlike the reddit attack, the attackers wouldn’t gain knowledge of passwords that might be in use for other accounts).

    Also, if you become aware of the compromise early enough, all you have to do is change mysecret, and the only people you have inconvenienced are those in the middle of account recovery. But that does depend on finding out early enough, which wouldn’t be a risk you would take with more important accounts. I guess with the system you propose, the only accounts possibly compromised in a database theft are those that are in the middle of account recovery, even if you are unaware of the theft.

  8. George White said 14 days later:

    I designed a very similar system for my current job and while I really love it, there are two problems. The first is spam filters, particularly those not under user control (think academic e-mail accounts). Badly designed or configured span filters prevent your user from ever receiving the recovery e-mail.

    And in certain unfortunate circumstances, this can lead to the second issue: your business may not be tolerant or patient with regard to customer service issues. We actually had to dismantle most of our recovery system because our business stakeholders felt that using the system was “too hard” and “unreliable” (despite the fact that we could demonstrate that it worked well for the vast majority of our customers). Sometimes even the best technical solutions can be undermined by business expediency. :(

  9. Ben Hoyt said 212 days later:

    Good article, thanks. We encrypt our passwords but have just been thinking about the best way to do password recovery.

    Your approach is one of the safest. But because email is sent in the clear over channels that could easily be tapped … it doesn’t seem like it’d be safe for sites that involve (say) money transations.

    Scenario: someone working at an ISP decides to get into John’s account at example.com. He clicks the “Send me a reset-password email” link, watches the clear-text email go past, clicks the token link in it, and he’s in.

    Sites like PayPal have a “security question”, which is annoying for users to enter the first time, not to mention remember—it’s kind of like a second password.

    How do you get around having to do this, while still being better-than-clear-text secure?

    (Joakim’s solution sounded good at first, because the short on-screen token could be sent HTTPS, but on second thoughts, of course you’re right - the attacker can easily initiate an account recovery request and see this too. :)

  10. Tom Moertel said 212 days later:

    Ben, you wrote:

    But because email is sent in the clear over channels that could easily be tapped … it doesn’t seem like it’d be safe for sites that involve (say) money transations.

    That’s right. The above procedure is intended for the recovery of accounts on “low-security” sites that grant accounts to anybody who can receive email: Slashdot, Reddit, Digg, etc.

    For sites that require stronger credentials to be granted an account (and that includes almost all sites that deal with money) this procedure isn’t strong enough. Instead, you’ll want to consider using backup passwords (e.g., answers to “security questions”), communication via more-trusted channels (e.g., U.S. Postal mail), and certain kinds of cryptographic protocols. You’ll almost certainly want to work with a reputable security expert who can give you other options and help you balance the costs and benefits of each. (Generally, the more-secure options tend to be less convenient for your users.)

    Cheers! —Tom

  11. proxy site said 247 days later:

    But because email is sent in the clear over channels that could easily be tapped … it doesn’t seem like it’d be safe for sites that involve (say) money transations.

  12. think twice before posting said 250 days later:

    Reddit deleted their blog entry, so here is the everlasting archive link http://tinyurl.com/ytplod

  13. Jonathan said 419 days later:

    I bank at an amazing bank in South Africa and they have a really good means for authentication (which would work in this case):

    1. User logs in and is SMSed an OTP (One time pin)

    2. User can continue to work in the account

    3. User requests to transfer money, at this point the OTP is required

    This is definitely not a doomsday method (the user may be held at ransom, or the phone stolen) but having two points of password verification is very hard to circumvent.

    Which leads to the following methodology:

    1. User signs up on website and enter one of the following:

    a. Cell phone number
    b. Jabber Id (E2E encryption is available in the protocol)
    c. Phone number
    d. SIP number
    e. ...

    And their email address

    2. User forgets password: hits the reset password button.

    3. A link is sent via email to activate password reset functionality.

    4. User clicks link.

    5. They are contacted via one of the above communication channels (Cell phone, jabber etc.).

    6. That channel then gives them another token that they need to use on the now open web site (from the link in the email).

    7. They enter the token and:

    a. Enter a new password
    b. Are presented with the new password.

    Methinks that will foil any virtual doomsday attack. Physical attacks are hard to prevent and I tend to think at this stage there is really nothing you can do for the user.

    This level of effort would only really warrant something like a bank, and is really extreme.

Trackbacks

Use the following link to trackback from your own site:
http://blog.moertel.com/articles/trackback/370

(leave url/email »)

   Comment Markup Help Preview comment