(Caution: Amateur security research ahead. Using it in a live system is not recommendable.)
I recently read “How to Safely Store a Password”, an article by Coda Hale. For years I’ve thought that salting and hashing passwords with MD5 or SHA-1 prior to storage was sufficient to thwart password-cracking efforts (in cases where the user-account database table is stolen or publicly divulged). Apparently, this approach is not much better than simply storing plaintext passwords (a practice widely scoffed at). It was fascinating to find out about a better approach, that of using bcrypt instead of ordinary hash functions. Unfortunately, it seems to me that bcrypt creates a new problem even as it solves an old one…
The New Problem
The use of bcrypt turns password-cracking into a computationally-prohibitive task for attackers. However, bcrypt also hurts defenders, for whom password-hash generation or verification is now much more expensive than with ordinary hash functions. A popular online service having thousands of users might need to acquire additional processing power simply to process user log-ins. Moreover, by using bcrypt a service would become more vulnerable to denial-of-service attacks. Attackers could tie up its servers’ CPUs through numerous, automated log-in attempts. (These would make the servers call bcrypt repeatedly, once for each of the many log-in requests.) Addressing this threat would seem to require problematic tradeoffs between security, cost and convenience.
Could there be a way of lowering a service’s computational bill while retaining bcrypt’s advantages? This article presents a system which might accomplish this. I haven’t heard of this approach and would like to know if I’m on to something (or if others have already devised equivalent systems). Be forewarned, I am not a computer-security expert. (Thank you for reading this article anyway.)
A Potential Solution
The following protocol attempts to reduce the frequency with which bcrypt is called by an online server. It ensures that clients also pay for the cost of using bcrypt. (The server still has to pay, but the client now has to “split” the computational bill with it. This could reduce the appeal and effectiveness of brute-force or dictionary attacks on live systems.) This protocol redesigns the account-creation, account-log-in, and password-reset processes for an online service.
- Joe Turing, a user (or a bot, perhaps), visits the account-creation page for SecureR, a hypothetical (yet surprisingly-popular) online service.
- Turing types in his desired username and password and submits them (securely) to SecureR.
- The SecureR server creates a bcrypted hash from the password, using a random salt value and the cost parameter currently mandated by SecureR’s security policy.
- The username, password, salt, cost, and bcrypted hash are stored in a record in SecureR’s user-account table. The record also includes at least two verification fields. One indicates whether the password hash has been verified (successfully computed by the client). The other indicates whether the account as a whole has been verified. Both fields are initially set to “false”.
- SecureR sends the salt and cost parameters used to bcrypt Turing’s password back to Turing.
- Turing (that is, his web browser) computes the bcrypt hash corresponding to his password and submits it to SecureR.
- SecureR compares Turing’s hash with the hash previously computed by SecureR itself. If the two hashes match, the password-hash-verification field is set to “true”.
- Once other essential checks (such as e-mail-address verification) have been successfully performed the account-verification field is to “true”. Turing’s account is now fully verified and active, and he can start using SecureR’s services. (For now I won’t suggest when and where additional verification steps should take place, since bcrypt is my focus here.)
- Turing types his username and password into the SecureR log-in page.
- Turing’s browser sends his username to SecureR.
- SecureR looks up the salt and cost parameters contained in Turing’s user-account record.
- SecureR sends the salt and cost values to Turing’s browser.
- The browser uses these parameters and Turing’s typed-in password to generate the bcrypt hash for Turing’s password.
- The browser submits Turing’s username and bcrypted hash to SecureR.
- SecureR directly compares the hash submitted by Turing with the one stored in his user record. If they match, account access is granted.
- Turing types his username into SecureR’s password reset page and clicks the Submit button.
- SecureR sends a verification link to Turing’s e-mail address. (This link is to verify that Turing himself initiated the reset process. This password-reset system never generates nor sends temporary passwords to the service’s users.)
- Turing checks his e-mail, and opens up the link with his web browser.
- The page brought up by the browser has a password field, into which Turing enters his new password and clicks on a Submit button.
- The password is hashed and verified using a process analogous to steps 3 through 8 in the account-creation process.
According to this protocol, SecureR’s server only runs bcrypt when an account is created or when a password is reset. During log-in attempts, it is the client (Turing’s browser) and not the server which runs bcrypt. The server performs a computationally-inexpensive direct comparison between the client-submitted hash and the hash stored in its database. Thus the server avoids paying the bcrypt bill when processing a log-in request. (In theory, the client could also avoid calling bcrypt during log-ins. The bcrypt hash could be stored by the client after generating it during the account-creation phase. The client wouldn’t necessarily have to recompute the hash each time the user logs in. In practice, it’d be easier to design the client-side code so that it recomputes the bcrypt hash based on the user’s plaintext password, rather than dealing with hash storage and retrieval. The bcrypt-induced client-side log-in delay is tolerable to each individual user anyway.)
Relocating bcrypt invocations from a frequent process (account log-in) to other, less frequent processes (account-creation and password-resets) reduces the risk of a successful DoS attack. It doesn’t eliminate the risk completely, though. The account-creation and password-reset processes are the new weak spots, and must be hardened. This is why the password-reset process is more complex (and mildly annoying to legitimate users) under this protocol. Additional security methods (such as CAPTCHAs and rate-limiting) could also help harden the system against attack.
A Request for Feedback
My proposed protocol omits certain security-related details which would be important in a production system. I’ve also omitted some tweaks which could further improve the protocol’s security. However, I’d first like to make sure that this protocol is essentially sound. Please let me know if you find any logic errors or problematic side-effects I’ve failed to account for. As I said before, I am not a computer-security professional, and would appreciate assistance from others who are further along.