The source code is here
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.
Also noteworthy is, that sample
is not secure, because of its predictability. If we wanted a proper salt SecureRandom would be a good option.
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.
If you want to see an example of how these methods are used in a real backend you should read part two
Top comments (13)
Hello!
In your Second approach - digest section, you have a typo error in your code :
Oh, I didn't catch that. Thank you!
I'd like to add a fifth approach: use an algorithm designed for passwords, like bcrypt, which takes care of salting for you, but it's also more computationally expensive with configurable complexity. I think bcrypt is still the default in Rails.
Using a single round of SHA-256 with all the existing hashing hardware acceleration (thanks Bitcoin!) isn't much secure nowadays.
You're right, I should have mentioned the bcrypt approach. My goal in these articles is to show the reader how the theory behind all the library magic works, to make understanding the principles easier. Thanks for the heads up!
Hello Anes!
Thanks for the post. I'm new in web development, and I've been working with bcrypt and similar libraries. Since these are the "do-not" do approaches, what would you recommend to make the password storing more secured?
Hey Anthony!
The last approach that is documented (peppering) is a relatively secure approach, if you want to do it by hand. My goal with this article is to demonstrate how you could do it by hand, so that beginners know the theory. But when I make a RoR application I also use bcrypt. I have an article about bcrypt in Rails in the drafts, which I will link in my post as soon as it is done
Thanks for the reply!
I'll start looking more into that approach, and see how it goes. I also think that OAuth2 could be a better approach. Maybe a combination of both.
Yes absolutely. I am working on a rails guide about using devise for user management etc. When that is done I will link it in this post and after that I am planning on making an introduction on 3rd party authentication (github, google etc.). Stay tuned if you are interested!
Awesome!
Thanks for putting this together! We all need those baby steps at first lol
Hello anes,
thank you for your article.
I've never used Ruby as a programming language, so it's interesting to see it combined with a password security approach. It kind of reminds me of Python :).
I found some typos(?!) in your article (Nothing really bad)
"...no not use this approach in any real software..."
This is just a small a typo: "do not use..."
"...He can't do a lot with the salts, even in plain-text, but we want to be extra."
I assume you meant: "but we want to be extra sure"?
Hey Akin,
Thank you for the feedback! I proof-read the article again and corrected a few minor mistakes I overlooked.
Why poeple still storing password? Maybe its time to move internet to passwordless ? I think, most of problem not a password storage, but how people creating password, or how they are storing them
I have to agree with you on multiple points: the biggest security risk is always the end user. While a software developer is educated in digital security, the average user wont be. And as a software engineer you should always look for a way in which you can get around storing passwords. Nonetheless it is important to store your passwords securely if you have to. If there is a data leak you are the person responsible for leaked passwords. Or another situation: you have a man on the inside, who only wants the password of a certain user. He can simply look at the users password and log in as him.