Slack was hacked. Reading the thread on HN I decided to discuss why hashing only passwords is impractical if attackers get read access to your users table or entire database.

Because it’s not the only way to hijack your account. reset_token, api_key and other kinds of tokens are usually stored in plain text and can be used as authentication credentials to set a new password or do something bad with account (transfer Bitcoins etc).

Attackers can reset victim’s password, fetch the reset_token value from database they have read access to and visit to set a new password.

Forget about User.find_by_token(token)

Use following approach for all sign-in attempts, API requests, even for session cookies (if you store session_id in the database)

type = password, reset_token, api_key, access_token, session_id etc

user_id = 123, homakov or

value = plain text value

  1. find user by provided user_id
  2. for type=password hash provided value with all the salt/pepper dance (read how to do it properly somewhere else). You need it to slow down dictionary/bruteforce attacks. For other random tokens md5(token) will be fine. Just don’t store it in plain text
  3. now compare it with type_hash column. As a bonus no need for constant time comparison - timing attack is mitigated by default.

Now even if your database is compromised you don’t need to rotate api_keys and other tokens.

Quick update - actually I was very wrong. There is no way to tell if someone hashes tokens with blackbox because they could hash it before searching and there’s also no need for user_id parameter.

Mar 27, 2015 • Egor Homakov (@homakov)