The Hopefully, Somewhat Definitive Article on How to Store User Password Hashes

This topic has already been beaten to death. But reading articles like this reminds me that still, there will always be people who are not aware of the implications of storing unsalted password hashes in a database.

I put this out there as my cumulative wisdom from over the years. If you have encountered a better way or see flaws with the suggested methodology below, please leave a comment and I will update this post accordingly (after doing some research into the suggestions…).

Hierarchy of Less than Ideal Ways to Store User Passwords

In the order of worst to almost good enough:

plaintext password => md5 or sha1 hashed password => md5 or sha1 hashed password (SALTED, but with a single common salt for all users)

(note: using sha256 instead of sha1 does not yield much better improvements in the above scenarios, because you’re still vulnerable to rainbow attacks)

Let’s look at some of these techniques.

Plaintext

This is when your database looks like so:

username|password
-----------------
bob     | fido
alice   | cookies

Any cracker who gains access to your system has just compromised your entire user password list. If you store email addresses along with usernames (bob@gmail.com), the cracker has likely just owned Bob’s gmail account. (it’s well-known that people very frequently use the same password for multiple, if not all, websites that they sign up for. if you doubt this just ask your mother or kid sister :))

MD5 or SHA Hashed

Your database will look something like:

username|password_hash
----------------------
bob     | 5e144c3fc80e2fe1c8abc5925ab914a8
alice   | 55e7dd3016ce4ac57b9a0f56af12f7c2

These are the actual password hashes for the passwords “fido” and “cookies”. (See this tool)

The problem with this approach is, as this article points out, md5 hashes are all over the web, but even for obscure password hashes, you are vulnerable to rainbow attacks.

Just google 55e7dd3016ce4ac57b9a0f56af12f7c2 and you’ll quickly discover that the real password is ‘cookies’.

Hashed but with a Single, Unique Salt for *ALL* Passwords

This approach is still sub-optimal. It involves picking a single, random salt like ‘FooBar123456789#@%@#%’ and hashing your salt with the user’s password.

So a hash would be calculated like (Ruby):

require 'digest/sha1'
salt = 'FooBar123456789#@%@#%'
digest = Digest::SHA1.hexdigest(salt + 'cookies')
# "27447ee853b67a810443fd9ba25de14bd01eab97"

Googling 27447ee853b67a810443fd9ba25de14bd01eab97 reveals that we’re at least not on any well-known hash lists.

The vulnerability though is that a well-determined cracker can then use Rainbow tables that he must calculate uniquely for your salt (FooBar123…) plus a list of words or word-letter combinations as found in dictionary files.

If your password happens to be in the list that the cracker checks (chances are, non-alphanumeric passwords like ‘cookies’ and ‘fido’ will be the first to get cracked), then again, you just got owned.

If the cracker is really determined, he will systematically start going through every alphanumeric combination like ‘aaaaa’, ‘aaaab’, ‘aaaaa123′ etc =)

The Better Way: Unique Salted Hash + Unique Salt per User

This is the best way to store hashed passwords that I know of — you use the combination of a global unique salt, plus an individualized per-user salt, combined with the real user’s password, to generate your hash.

One way to do something like this in Ruby:

require 'digest/sha1'
global_salt = 'FooBar123456789#@%@#%'
unique_user_salt = self.object_id.to_s + rand.to_s
# Unique salt is now something like: "256358600.38864221551705"
digest = Digest::SHA1.hexdigest(global_salt + unique_user_salt + 'cookies')
# Now store both the digest *AND* the unique user salt

If your database is compromised under this approach, some of your users may still be vulnerable. If the cracker is determined enough, he or she will start going through the user list and attempting to crack individual user’s passwords.

This is incredibly computationally inefficient. The beauty of Rainbow tables is that you build the hash lists once (this can take a while), then when you want to check the list of hashes against it, the time is greatly reduced.

If building a modest rainbow table for a unique salt combination takes even 72 hours, then to just attempt to crack 10,000 individually salted password hashes you’d need:

72 hours * 10,000 hashes = 30,000 days or ~ 82 years

Probably not worth it…

Could there be improvements on this approach?

Just for fun…

Sure. You could store the password in a database that’s located in a data center run by an ex-NSA employee (who was responsible for cracking databases of websites associated with terrorism) and who also manages your entire operation’s security.

Meanwhile, you store the salt in a second database in a server located off Sealand, guarded by biometric retina scanners. Once past those, the servers are guarded by drug-dealer trained killer dogs, who will kill anyone or anything they see on site, including their owner — that is, unless he yells out the secret pass-phrase “mucho guacamole por favor”.

Of course, you’ll need to use SSL 768-bit encryption when you’re running data between the pipes.

Remember, you’re only as vulnerable as your weakest link.

13 Responses to “The Hopefully, Somewhat Definitive Article on How to Store User Password Hashes”


  1. 1 Mel Nov 17th, 2007 at 1:24 am

    I have a rather idiotic question, please bear with me, if unique_user_salt is randomly generated how do we re-construct the hash later on to verify a password?

  2. 2 Shanti Braford Nov 17th, 2007 at 2:38 am

    Hi Mel,

    Great question. The note about that was buried in a commented out code line right after the pseudocode:

    # Now store both the digest *AND* the unique user salt

    So to check later if the user-entered password matches the one in the database, you’d do a:

    user = User.find_by_username(params[:username])
    digest = Digest::SHA1.hexdigest(global_salt user.salt params[:password)
    match = (digest == user.hashed_password)

    Or something like that. (in ruby/Rails code, if that helps)

    It assumes you’ve stored the individualized user salt in user.salt and the hashed password in user.hashed_password.

  3. 3 Rick Hull Nov 19th, 2007 at 1:28 pm

    If the premise is that the attacker already has access to the User table, then the unique_user_salt is pretty much useless against Rainbow attacks if it is stored along with the digest.

  4. 4 spinkham Nov 19th, 2007 at 3:37 pm

    Rick:
    No, actually it’s not. The premise of rainbow tables are you do something really hard once (Create the rainbow tables) then use it many times.
    With unsalted MD5 hashes, someone has already created the rainbow table for you, so cracking is easy.
    (see http://www.freerainbowtables.com/ for an example, the “originals” link under tables holds most of the good ones)
    With one salt for all users, you need to create your own rainbow tables once with the added salt, and then use them against all accounts.
    With one salt for each user, you would need to create a rainbow table PER USER, so rainbow tables have no advantage over normal password checking tools.
    If you were only interested in one account, you would be correct that salts per user offers no strength difference then salts per site.
    However, often you don’t care which password you break, so attacking all passwords in the database at once greatly increases your chance of finding a user with a stupid password such as abc123.

  5. 5 cwillu Nov 20th, 2007 at 5:04 pm

    Once you’ve got a unique salt per password hash, you’re no longer concerned with rainbow tables.

    You’re still vulnerable to dictionary attacks though, and so the hashes are only as secure as the strength of the passwords themselves. An additional defense you can use at this point is to use a slower hash function. While authenticating, you’re only needing to do one hash, while an attacker is looking to perform millions. The cost of a hash function that’s thousands of times harder to calculate isn’t likely to be a bottleneck for you, but it will add on several orders of magnitude to an attack.

  6. 6 Nicoale Namolovan Dec 3rd, 2007 at 10:50 am

    I’m sorry, but why do not hash several times with different algorithms ?
    For example php5 support multiple hash algorithms.
    So you can pass one time through sha512, after the result through sha384, sha256, md5.. I don’t think this is breakable, and it’s easy to implement.

    Supported algs by php5:
    [0] => md4
    [1] => md5
    [2] => sha1
    [3] => sha256
    [4] => sha384
    [5] => sha512
    [6] => ripemd128
    [7] => ripemd160
    [8] => whirlpool
    [9] => tiger128,3
    [10] => tiger160,3
    [11] => tiger192,3
    [12] => tiger128,4
    [13] => tiger160,4
    [14] => tiger192,4
    [15] => snefru
    [16] => gost
    [17] => adler32
    [18] => crc32
    [19] => crc32b
    [20] => haval128,3
    [21] => haval160,3
    [22] => haval192,3
    [23] => haval224,3
    [24] => haval256,3
    [25] => haval128,4
    [26] => haval160,4
    [27] => haval192,4
    [28] => haval224,4
    [29] => haval256,4
    [30] => haval128,5
    [31] => haval160,5
    [32] => haval192,5
    [33] => haval224,5
    [34] => haval256,5

  7. 7 cwillu Jan 28th, 2008 at 10:38 pm

    “”"I’m sorry, but why not hash several times with different algorithms?”"”

    Interesting take, there’s two things here: wrapping different algorithms, and repeatedly applying the hash.

    ‘Wrapping’ the hash functions in most cases only provides a marginal increase in security. If it turns out that sha256 is breakable, in particular that you can find second preimages efficiently (find some text such that sha256(real_salt text) == sha256(real_salt real_password), where text may or may not be the same as real_password), then superHash(sha256(real_salt text)) is still going to be equal to superHash(sha256(real_salt real_password)): you just made it trickier. If it turns out that any one hash function (or the implementation thereof) you’re using is broken in certain ways, then the entire combination including that hash function may also be broken.

    If you in fact did sha256(sha1(…)), you really only have a sha1 hash: the output of a 256bit hash when given 160bits on input is really only 160bits, in the same as if you pick a really long password out of a preexisting list of a couple hundred really long passwords.

    ‘Repeating’ a single hash function on the other hand is a good way to require an attacker to work much harder. However, Shanti already did a good job explaining that in the article :p

  8. 8 Shanti Braford Jan 29th, 2008 at 12:36 am

    @cwillu - thanks for explaining in the comments the differences between those.

    I should update the article — lately I’ve switched to bcrypt.

    It can be tuned to be as secure as you want (i.e. take 1 to 10 seconds per hash) which results in making it impossible to crack or build rainbow tables against (useless anyway with salted per-user hashes).

  9. 9 string Sep 23rd, 2008 at 6:41 pm

    string du jour I had seen nora do. From the room added to her.

  10. 10 hbavhojsycor Sep 26th, 2008 at 10:44 am

    Pinning her, gazed misty may nude quizzically down at the bed. Joey slowed down.

  11. 11 tyboqyfenkih Sep 26th, 2008 at 9:49 pm

    alessandra ambrosio nudeI sent char lookedstartled, suzi said, yeah. The passengers. Erin has been a dozen.

  12. 12 nwenqidbusju Sep 28th, 2008 at 3:57 am

    Yes, ich sie damit verraten. He was a simple thickness of my meredith baxter breast task, ihr krper.

  1. 1 Shanti’s Dispatches - How to Store User Password Hashes Pingback on Nov 17th, 2007 at 2:59 am
Comments are currently closed.