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:
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:
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:
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.
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
$2b says this is a
bcrypt hash (
$2a does too).
$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:
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:
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 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
If you have any feedback about this or any other Compose article, drop the Compose Articles team a line at firstname.lastname@example.org. We're happy to hear from you.