Saturday, December 25, 2004 6:21 PM bart

PSP Episode 5 - How to store passwords?

How to store passwords?

A pretty well-known scenario: you're implementing a great website with forms authentication and you need to store the users' passwords somehow. But how? In this post I'll show you common techniques to do this in a secure way.

First solution - don't store passwords

Might sound ridiculous but in some scenarios it definitely makes sense. If you can avoid it, do it. Possible ways to implement this strategy are to use Windows authentication (intranet, extranet scenario), to use .NET PassPort or some other authentication system that does the work for you (some directory service that you can try to bind to, e.g. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secmod/html/secmod16.asp).

Okay, the first solution wasn't fair, we need a database

Nice, so you need to store the user credentials in a database. I'll not ask any questions on the security of your database right now (will be covered in future episodes on securing the DAL), let's just focus on how to keep the passwords secure inside the database. The keyword here is encryption. As you probably know, there are some different encryption categories. First of all we can consider the encryption algorithms that encrypt data in an irreversible way (hashing). Secondly, there is encryption that allows you to decrypt again. In this category you'll find symmetric and asymmetric encryption. A discussion...

Do you need to be able to retrieve the password later?

This is the number one question you should ask yourself. I hope the answer is definitely: "No". Maybe you think it is yes now, but in most cases you can avoid it. If you thought the answer for you was yes, you were likely thinking of the "Forgot password" link on your login page. A user forgets his password an you need to send the password back to the user's mail address for recovery. That's fine, but there are several other ways to recover a lost password without requiring the original password:

  • Use a secret question. Right... But how to keep the answer really secure? The answer is you can hash the answer to the secret question (see further for hashing) but this makes the answer case-sensitive if you don't look out (consider to lower case before hashing, which will make this more user friendly - but less secure). The security of this technique has to do with the fact that the user will be redirected to a page where the password can be reset (without requiring the original question), eventually using an extra step by using e-mail to send the link to the resetpassword page. There is quite some plumbing involved in here to maintain the user profile's state in the database but if you try to think about it in a logical manner, it should not be "mission impossible". If you're not using e-mail as an intermediate step, the secret question possibly becomes the weakest link in your security implementation (when the answer is correct, the user can reset the password). In this case, make sure the answer that the user provides is long enough (passphrase characteristics). But if you can, consider another roundtrip to the user by e-mail since only the real user should be able to receive the e-mail to click the "resetpassword link". Typically that link contains a parameter with the user's ID and a generated random value that has to match a field in the database that is stored in the user's row (e.g. resetpassword.aspx?user=12345&resetcode=13G2IUE91AE209PS49D). An overview of the ideal situation:
    • Click on "Forget password".
    • Ask the user for his username or password to retrieve the user's secret question. Show the question to the user and ask for the answer.
    • Check the answer by comparing hashes (of the lower-cased value, trim for spaces in the begin and at the end), i.e. the hash of the entered answer and the stored hash value in the database.
    • If the comparison succeeds:
      • Set the profile state to "Resetting" by modifiying some field in the database. This allows to provide resubmit reset password link functionality etc.
      • Generate a random value (see further on how to do this) and store it in the database on the user's row in some ResetCode field. Ideally, you store the time of the code generation as well, so that you can expire the reset code.
      • Compose an e-mail with the link in the aformentioned format containing the user ID and the resetcode and submit.
      • Notify the user about the fact that an e-mail was sent.
    • The user comes back to the site on the resetpassword page. Over there, retrieve the user's reset code based on the user ID parameter and compare it with the resetcode parameter in the URL. Also check if the user's account is in the "Resetting" state. If this is right:
      • Ask for a new password twice and perform the typical comparison and complexity checks.
      • Hash the password and store it in the database, reset the user's profile state to "Normal" and drop the reset code from the database.
      • Redirect to the logon page or perform automatic login
  • An alternative is to send a mail to the user with a reset password link without using a secret question first. This is in fact the same as the technique above, but less complex. It also relies on a resetcode and all the associated plumbing.

If you still need to be able to retrieve the password later on, you'll have to rely on a reversible encryption algorithm, typically a symmetric one such as 3DES. As the matter in fact, this moves the problem to another level, i.e. where to store the key used to encrypt/decrypt the user's password? I won't go into detail on this (in this post I assume you go for hashing) but I'll cover this later. The answer is DPAPI. But remember, you use DPAPI to store the key, not the passwords (this is because DPAPI relies on a machine secret, so in case of a machine crash the information can't be retrieved again or when using a server farm, encryption/decryption across multiple machines will fail). A common scenario where to use symmetric key encryption with DPAPI is to store credit card numbers and other sensitive data that you need to be able to retrieve again later on.

So, we go for hashing

I hope everybody who reads this blog knows about hashing. To summarize, hashing is a one-way function that converts an input value to an output value in a way that it is computationally impossible to retrieve the original input value back when you have the resulting output value. Two different inputs should be converted to a different output value as well and a little change to the input should have a big impact on the output. Hashing algorithms are fairly complex mathematical stuff in order to have a good distribution in the function domain and to comply to the criteria I mentioned. Two famous hashing algorithms are MD5 (Message Digest 5, the version after MD4, by Ron Rivest - on of the RSA guys) and SHA1 (Secure Hash Algorithm). Let's show how we can use hashing to store passwords in a secure way (first attempt):

  • Set a user's password (registration or profile update page):
    • Ask for the password with all the checking involved (complexity, ask it twice, mask passwords, don't forget server-side checking, etc).
    • Take the password an hash it using System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(string, string). I like this method name :-). The first parameter takes the password string, the second one "MD5" or "SHA1" depending on the algorithm you want to use.
    • Save the hash instead of the clear text password.
  • Login page:
    • Ask the user for his user name and password.
    • Retrieve the password hash from the database based on the user's name (because it's hashed the on-the-wire attack surface between the web server, application server and database server is minimized, i.e. no clear text password sniffing possible). Don't send the password to the database to compare it over there (can be sniffed on the network).
    • Hash the password that the user entered and compare it with the retrieved hash value.
    • Authentication passes when the comparison succeeds and fails otherwise.

This seems to be great, isn't it? Well, it is indeed safer than the storing the clear text password in the database. However, it can (and should) be better, read on.

Buy a dictionary

As I mentioned earlier, a hash is a one-way encryption system. So, when you have the hash value, you can't get back to the original value. Unfortunately this is only theory. We all know that users choose passwords that are quite predictable (English - or any other language - words, names, simple letter combinations). Now, what about creating a dictionary based on a long list of words mapped on their MD5 and/or SHA1 values? Put an index on the hash values and you're ready to go. When you have found the hash values of a bunch of users (e.g. by hacking into the database server or by stealing the database or just because you are the DBA on a hosting network and you're a bad guy) it's easy to join the dictionary with the user table to map users on their clear-text passwords if these are known. How to solve this problem? The answer is salting...

Salt-n-pepper

Well, drop the pepper. We only require salt. Salt is a random chunk of data that can be used to mitigate the risk of a dictionary attack. The general idea is not that difficult:

  • Set a user's password (registration or profile update page):
    • Ask for the password with all the checking involved (complexity, ask it twice, mask passwords, don't forget server-side checking, etc).
    • Generate a hash value (see further) and concatenate it with the user's password.
    • Take the password an hash it using System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(string, string). I like this method name :-). The first parameter takes the password string concatenation of the password and the salt,  the second one "MD5" or "SHA1" depending on the algorithm you want to use.
    • Save the hash instead of the clear text password, together with the salt (thus you need two fields, one for the hash and one for the salt).
  • Login page:
    • Ask the user for his user name and password.
    • Retrieve the password hash from the database based on the user's name (because it's hashed the on-the-wire attack surface between the web server, application server and database server is minimized, i.e. no clear text password sniffing possible). Don't send the password to the database to compare it over there (can be sniffed on the network).
    • Retrieve the salt from the database based on the user's name.
    • Hash the concatenation of the retrieved salt and the password that the user entered and compare it with the retrieved hash value.
    • Authentication passes when the comparison succeeds and fails otherwise.

An attacker who steals the database now has more difficulties to perform a dictionary attack. Now we need to take all words from a dictionary, concatenate the words with the salt (on a user-per-user basis), hash them and compare. The longer the salt, the more time it will take to calculate the hash. Another problem for the attacker is to get to know how the concatenation of the hash and salt is done (in front of the password, at the end of it, ...). A dictionary attack is still possible but not with a static dictionary, therefore eliminating the real risk.

How to generate the salt?

A hash should be cryptographically strong. Because of this, you should never use System.Random to create the salt (not strong enough). Instead, use the System.Security.Cryptography.RNGCryptoServiceProvider class as follows:

   RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
   byte[] buf = new byte[size]; //size is length of the salt
   rng.GetBytes(buf);
   string salt = Convert.ToBase64String(buf);

Sample

Take a look at http://www.bartdesmet.net/download/hash.aspx for a sample of the techniques described in this post. The code is listed on the page as well.

MD5 or SHA1?

Nice discussion... MD5 hashes are shorter than SHA1 but MD5 is faster than SHA1. Now, if security should be as high as possible, MD5 even with hashing should not be your choice. Consider to use SHA1 instead. MD5 is considered to be unsafe since August 2004 (publication of article by Wang, Feng, Lay and Yu) because of the discovery of collisions. For more information about this, take a look at http://eprint.iacr.org/2004/199.pdf.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# Bart's PSP (Personal Security Push)

Monday, December 27, 2004 6:34 AM by TrackBack

# Bart's PSP (Personal Security Push)

Monday, December 27, 2004 6:35 AM by TrackBack

# re: PSP Episode 5 - How to store passwords?

Monday, December 27, 2004 9:58 AM by bart

hi,

also consider using PasswordDeriveBytes - this takes a salt and a number of iterations to generate the hash...

bye
dominick

# re: PSP Episode 5 - How to store passwords?

Thursday, February 03, 2005 7:16 AM by bart

Can I dare to present a little implemenation at

http://weblogs.com.pk/fahad/archive/2005/01/17/1266.aspx

?

Btw, very nice effort.

# re: Saving Passwords...

Thursday, February 03, 2005 7:21 AM by TrackBack

# re: PSP Episode 5 - How to store passwords?

Wednesday, March 09, 2005 3:33 PM by bart

I'm using FormsAuthentication.HashPasswordForStoringInConfigFile (using SHA1) to encrypt passwords before storing in the database. How do you decrypt a password stored this way?

Thanks.


- Sue

# re: PSP Episode 5 - How to store passwords?

Wednesday, March 09, 2005 5:15 PM by bart

The answer is pretty simple, you can't. And in fact, this is just the reason why you should use hashing. Hashing is a one-way function that is irreversible, so you can't go back to the original password (in computationally reasonable time). If you need password reset functionality, you'll need to generate a random password for the user and send it via e-mail to the user, or you can work with secret question/secret answer techniques too.

# re: PSP Episode 5 - How to store passwords?

Tuesday, June 21, 2005 3:38 AM by bart

In a web application you have to send the password to the server in clear text unless you encrypt it with a client-side function (JS). And then you still run the risk of someone sniffing packets, grabbing your SHA1 hash value, and sending that to the server as if they had run the script on the webpage.

# RealTime - Questions: "Encrypt plain text passwords already in database?"

Pingback from  RealTime - Questions: "Encrypt plain text passwords already in database?"