DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,547 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for How (not) to store passwords
anes
anes

Posted on • Updated on

How (not) to store passwords

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jnv profile image
Jan Vlnas

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.

Collapse
 
aneshodza profile image
anes Author

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!

Collapse
 
thomasbnt profile image
Thomas Bnt

Hello!

In your Second approach - digest section, you have a typo error in your code :

def register
  encrypt = password
-  sha2_digest = Digest::SHA2.new(256).hexdigest(encrpyt)
+  sha2_digest = Digest::SHA2.new(256).hexdigest(encrypt)
  @password = sha2_digest
end
Enter fullscreen mode Exit fullscreen mode
Collapse
 
aneshodza profile image
anes Author

Oh, I didn't catch that. Thank you!

Collapse
 
anthgrim profile image
Anthony Grimaldi

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?

Collapse
 
aneshodza profile image
anes Author

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

Collapse
 
anthgrim profile image
Anthony Grimaldi

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.

Thread Thread
 
aneshodza profile image
anes Author

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!

Thread Thread
 
anthgrim profile image
Anthony Grimaldi

Awesome!
Thanks for putting this together! We all need those baby steps at first lol

Collapse
 
incrementis profile image
Akin C.

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"?

Collapse
 
aneshodza profile image
anes Author

Hey Akin,
Thank you for the feedback! I proof-read the article again and corrected a few minor mistakes I overlooked.

Collapse
 
edvinasnet profile image
Edvinas Cernauskas

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

Collapse
 
aneshodza profile image
anes Author

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.

What image format should you use in your next project? πŸ€”