Why is securely storing passwords so necessary?
A question I used to ask myself is: Why do I need to securely store my passwords in my database? With proper security no one should be able to read it anyways. Back then I was an amateur coder, who has never worked on any real projects, but rather only on some smaller stuff.
After starting my first software developer job I quickly realised how naive I was. A developer should always be prepared for the worst, especially when being responsible for security.
Let's say you run a database for a social media platform and a group of hackers gets into the database. It's not important how they got in there, but often there are insiders that are responsible for such attacks. Now the hackers extract all the passwords out of your database. You, being a naive coder, stored all of them in plain-text, which means that you are now responsible for the leak of thousands, if not millions of passwords.
First approach – encryption
Now from the example above, you realised your rookie mistake. You use an encryption algorithm, such as DES to secure your passwords. That may seem like a good idea, but it really is not. Even if your insider does not have the encryption and with that also the decryption key, the hackers can simply brute force it. Or they make their life even easier: one of them signs up on your social media service, which gives them the input and output, making it much easier to get the key. Asymmetric encryption isn't a good approach either, because the passwords are still decrypt-able
Second approach – digest
After that you try to simply digest the passwords.
In ruby that looks like following:
def register encrypt = password sha2_digest = Digest::SHA2.new(256).hexdigest(encrypt) @password = sha2_digest end
That is almost as useless as just storing them in plain text, because they basically are plain text. Nowadays there are huge, precomputed, tables with plain text and the digest version of those, called rainbow tables. An attacker can simply look up your hash and find the used password. Crackstation has 15GiB of strings and their "digestion"!
Third approach – salting
Then you start becoming smart. Your next approach is also adding a salt to every password. A salt is a random String of characters (preferably very long), which is thrown into the password before the digest happens. With a good salt, most rainbow tables are completely useless. Because a digest changes completely if we only change one character in the input String, we can simply store the salt as plain text in the database.
In ruby you can add a salt as follows:
def register salt = ('a'..'z').to_a.sample(8).join encrypt = password + salt sha2_digest = Digest::SHA2.new(256).hexdigest(encrypt) @salt = salt @password = sha2_digest end
You need to be careful with that approach: Making a weak salt is not as bad as no salt, but it's also not ideal. The rainbow table definitely contains password123, but he may also contain salt-password123. In our case, we set a very weak salt, so please, do not use this approach in any real software.
Fourth approach – peppering
So now we have our salting, but if the hacker gets access to the database, he can also see the plain-text salts. He can't do a lot with the salts, even in plain-text, but we want to be extra secure. We can achieve that by adding a pepper. That is another string, working very similar to a salt, but with one small, but important difference: it is not different from user to user, but rather just a hard-coded string in our code. In ruby that may look like following:
PEPPER = 'mypepper' def register salt = ('a'..'z').to_a.sample(8).join encrypt = password + salt + PEPPER sha2_digest = Digest::SHA2.new(256).hexdigest(encrypt) @salt = salt @password = sha2_digest end
Disclaimer: Once again, this pepper is not secure at all. Please use a longer and more random pepper on real software.
How a login works
Now if a user wants to login, we can't simply compare our input with our password. On one end, we added extra strings, on the other side, we used a digest, which makes it impossible to convert it back to plain text. We need to take the input, append the salt and pepper, digest it and then compare with our database entry. That may look like this:
PEPPER = 'mypepper' def login encrypt = input + @salt + PEPPER sha2_digest = Digest::SHA2.new(256).hexdigest(encrypt) @password == sha2_digest end
If the password and our digest match, we know that the user entered the correct password, without even knowing the password, which makes it very secure.
Now, if you want to see an example of how these methods are used in a real backend read part two of this blog entry on DEV.
If you want to take a look at the source code you can find it on GitHub.
This blog post was first published on DEV.