Thursday, June 26, 2008

always encrypt passwords

Let's pretend that you run a website or newsletter. For fun, let's call it: Gizmo's Tech Support Alert...

Subscriptions to your newsletter are free, but you also have a premium edition which costs $10 per year. You don't want to worry about the security hassles of storing financial information, so you off-load all the financial transactions to a third party.

Since you're not storing any "confidential" information in the user accounts, you assume that the worst thing that an attacker could do with login information is cancel an account. So, you decide to make life a little easier for you and your subscribers.

If someone types in their email and hits the "forgot my password" button, you email them their current password. And, to encourage your premium subscribers to renew, when their subscription is almost over, you send them a friendly email reminder with:

  • a link to the login page
  • their username
  • their password

All seems well. The users don't have to remember their login information, because as long as they remember their email, you can send it to them. This is a nice user experience, but it is ABSOLUTELY the WRONG thing to do.

If you don't encrypt your passwords, you will eventually make some nerd angry enough to write an article about it, and he will use your newsletter as an example (wink).

Unencrypted Passwords are Evil

Even if you don't store confidential information on your site, it is still negligent to store (or email) unencrypted passwords. To make matters worse, if you send an email to your paying subscribers to encourage renewal, that exposes account information in unsolicited emails.

If an attacker gets access to the database or emails, they would have the login information for your subscribers. At the very least, an attacker could start canceling people's subscriptions to the newsletter, and that will be a problem for you; but your users will have a much bigger problem if they used the same password for the newsletter subscription as they did for another (more important) account.

While you may be savvy enough to have a different password for every website, lots of other people look at their limited time and memory, and don't realize the risks of reusing passwords. Even if you advise them to have unique passwords, the majority of users will continue to use the same password on multiple sites. So it is the responsibility of the website developers and operators to keep passwords safe, by not storing them and by not emailing them.

How to Handle Passwords

First off, assume that you don't know how to safely manage passwords -- even the smart folks at Mensa seem to have gotten it wrong. There are a lot of hard won lessons in website security that you don't want to repeat. So hop on to your favorite search engine and start reading (search for things like: secure hashing passwords). Then find and use existing tools and libraries for your website's security.

In case you can't find any existing security solutions, or if you still want to brazenly create your own, here is a list of the things your website should do...

When a user creates a new account:

  • require an email address and send a link to that email address to activate the account
    • make it clear that you will never sell or give away their information unless required by law
  • store a hash of their password using bcrypt (or at least sha-1)
  • use salt to prevent rainbow table attacks
  • use a turing test (e.g. captcha) to prove your new user is an real person
    • note: captcha is not 100% reliable (and getting less so)
    • consider alternatives like word puzzles and image identification

When a user tries to login:

  • apply the hashing algorithm to their submitted password and compare it to the stored hashes
  • give the same error message when the username is wrong or the password is wrong
    • if you tell them which part of the login was wrong, they can guess to find usernames
  • when the login is invalid, insert a short delay before displaying an error, to slow down bots
  • lock an account if it fails on several consecutive login attempts
    • this lock can: be self-expiring, require administrative action, send an email to the user, or require captcha to unlock
  • after several consecutive login attempts from a single IP address fail (on one or more usernames), block that IP
    • this block can be self-expiring, require administrative action, or require captcha to unblock

When a user forgets their password:

  • have them submit their email address
  • then show them a message explaining how long it may take to get the email
  • don't tell them if the email is invalid (or an attacker could keep guessing to search for valid email addresses)
    • just say: "if you don't get the email, check the spelling of your email address and try again"
  • send the email: it should explain why they received it and contain a link to a Reset Password page
    • the link should NOT include any account information, like email address or username
    • the link should include a UUID to guarantee that no one can guess it
    • after a certain amount of time (e.g. 15 minutes) the link should expire
    • clicking the link after the time expires will open a page with a link to the Forgot Password page and a message explaining the expiration
  • don't send the forgotten password email to a single user more than once until the previous "forgot password" link has expired

These requirements are tricky, and I've probably screwed up some and entirely omitted many others. This is a wheel that doesn't need reinventing. So I'll say it again, find and use existing tools and libraries to handle the security on your website.

You don't want your site be some nerd's example of a security faux pas.