You may get pwned! At least protect passwords with bcrypt.

Passwords are precious. They not only protect access to the services you may offer but also because users have a bad tendency to reuse them, they may be the keys to a much wider domain of resources, accounts, and tangible assets. So when a user does create a password with you, you have to assume the worst, that that password is used in a hundred different places and that you do not want to be the service provider that allowed someone to get hold of that password. This process starts but doesn't end, by not storing plain text passwords.

The amount of effort required to protect passwords with some form of encryption is small compared to the risks. Even if what you are building is just a prototype that you know you will throw away, there isn't any reason to store exposed passwords in a database. Sometimes those prototypes become something more.

If You Aren't Convinced Yet This Should Scare You

Here is are the top ten sites with exposed email/password combinations per haveibeenpwned.com:

topTen

This should spook anyone entrusted with the storage of usernames and passwords no matter how small the application. If one looks even closer there is more to be concerned about:

linkedIn

The thing of real note is the SHA1 storage of passwords without a salt. Suffice it to say this is bad (and in their defense, it changed right after this apparently and it was some years ago). Almost all of those were cracked.

Which means it ended up looking something like this:

pastes

This is what can happen if you don't do something about this and ignore the minimum steps.

The Simplest Answer: bcrypt

bcrypt is an intentionally slow hashing function. While this slowness sounds paradoxical when it comes to password hashing it isn't because both the good guys and the bad are slowed down. These days most password attacks are some variant of a brute force dictionary attack. This means that an attacker will try many, many candidate passwords by hashing them just like the good guys do. If there is a match, the password has been cracked.

However, if the bad guy is slowed down per try that is amplified when millions and millions of attempts are made, often to the point of thwarting the attack. Yet the good guys probably won't notice on a single attempt when they try to log in.

Python and bcrypt

The natural place to hash a password is in the application tier. A simple example in Python:

pip install bcrypt

import bcrypt

passwd = b"helo"

hashed_passwd = bcrypt.hashpw(passwd, bcrypt.gensalt())

print hashed_pwd  

Generates this:

$2b$12$lVpvD4gOZZZ4IJUr94fBeeZbKofJBNcuCeq0ylhJxLJC2JjuEoive

The $2b says this is a bcrypt hash ($2a does too).

The $12$ encodes the fact that this password was hashed with 12 rounds which are logarithmic. This is the part that allows for the slowness to be encoded in the actual output which gives some cause for longevity as compute power increases. You can set it to more by passing the number of rounds to the bcrypt.gensalt(14) which would have been a factor of 2 more than the default.

The next 22 characters:

lVpvD4gOZZZ4IJUr94fBee

are a salt which helps prevent pre-computed dictionary attacks. Each dictionary would have to be computed for just this salt combined with each word in the dictionary and then computed.

The remaining 32 characters are the actual hash of the original password and the salt through the number of rounds:

ZbKofJBNcuCeq0ylhJxLJC2JjuEoive

This is what is actually compared after all of the computations.

Hash and Compare

Using python to check if a submitted password is good is simple too:

if bcrypt.checkpw(passwd, hashed_passwd):  
    print "good password"
else:  
    print "bad password"

The above takes the inbound password from the user to be checked. We call it passwd. Then it takes the previously computed and stored hashed_passwd from the database. This would have been looked up via something like a username or email key from the database (this isn't shown) and supplied to checkpw. checkpw reads the salt, hashes the password, and compares it to the previously computed hash. If it matches, then we'll get good password. That's it. Variants of this are available in almost every language.

Nothing Is Perfect

While there are other options, some more sophisticated and some less, bcrypt strikes a nice balance currently of good defaults out of the box and a solid track record out in the wild. It doesn't take much to use it, and it is available almost everywhere. So, there is no reason to store a plaintext password with such an easy solution as bcrypt available.


If you have any feedback about this or any other Compose article, drop the Compose Articles team a line at articles@compose.com. We're happy to hear from you.

Image by PDPics