wiiman
wiiman

Reputation: 111

md5 hashing using password as salt?

md5($password.md5($password))

is this good enough for password hashing? I am not asking for comparing this to something like bcrypt.

if it is not secure, tell me why.

Upvotes: 11

Views: 31976

Answers (7)

mdfst13
mdfst13

Reputation: 890

if it is not secure, tell me why.

It would be straightforward to make a rainbow table for it. It calls md5 twice. That's extremely quick. By contrast, the PHP default was to call the hasher a thousand times. That recently increased to four thousand times, and it is possible to override that to make it stronger.

Using the password as salt is bad because you can make a rainbow table over just the passwords. You've effectively made the rainbow table for a "salted" password as small as the rainbow table for an unsalted password. In other words, it's not actually salted. You've just changed your hashing function from md5 to md5($password . md5($password)). That roughly doubles the processing time to create the rainbow table. Adding a single character to the length of the password increases the time more than sixtyfold. And the new default hasher for PHP is more than four thousand times slower.

The rainbow table would be the same size as the one for a regular unsalted password. For real salts, you need a new rainbow table for each possible salt value. In other words, a single-letter (lower case) salt would require twenty-six rainbow tables. That increases exponentially with the number of characters.

Note that the worst case for brute forcing the password is the same as generating the rainbow table. If you only need to compromise one password, just brute force it. If you need to compromise two or more, generate the rainbow table. E.g. if you have an entire database worth of passwords, generate the rainbow table to crack all at once.

In general, using the ID would be better than the password, as it's different for each account. However, it's also available in the database, so no better than a random salt that is included in the password hash. And it's more subject to being changed than the random salt, because it's not bundled with the hash. So no more security and less robustness than including the salt in the hash.

Incidentally, we've also moved away from password complexity. Now the suggestion is to use long passwords (sixteen characters or more). In part, this is because it is relatively easy to evade the complexity requirements. E.g. 1337#Newbs! has digits, symbols, and capital and lowercase letters. But it's also only eleven characters and composed entirely of three dictionary words (albeit one in 1337 and another as a symbol) and a punctuation mark. Its actual complexity is less than forty bits much less correcthorsebatterystaple's forty-four.

Enforcing password length and using PHP's built-in functions password_hash, password_verify, and password_needs_rehash is now the standard. The php.net docs for password_needs_rehash has an example that uses all three of those together for password verification. Just use that and the equivalents for creating a user and updating the password (which only use password_hash). The only real variation is how you store the hash.

Using that pattern, password hashes will automatically get updated when the requirements get tougher. And if an insecure password hash persists too long, you can disable the account.

Upvotes: 0

Your Common Sense
Your Common Sense

Reputation: 157864

Although it seems quite enough to me, it will be in danger in case if someone precomputed a rainbow table based on the same algorithm (what is quite possible). So, I'd rather use an email for the salt which seems pretty secure yet usable. Paranoids may add some constant site-wide salt.

People often makes too big deal out of password salt (in theory), while in their applications they allow simple passwords and transfer them in plain text over insecure HTTP in practice.

Every freakin' day I see questions regarding salt or hash.
And not a single one regarding password complexity. While

The only your concern should be password complexity.

Why? Let me show you.

extraordinary good salt + weak password = breakable in seconds

It is always assumed that salt is known to attacker. So, by using some dictionary of most used passwords and adding [whatever extra-random-super-long] salt to them, a weak password can be discovered in seconds. Same goes for brute-forcing short passwords.

just sensible salt + strong password = unbreakable

Quite unique salt makes precomputed tables useless and good password makes both dictionary and brute-force attacks good for nothing.

Upvotes: 4

Don Kirkby
Don Kirkby

Reputation: 56640

The reason to use a different salt for each user's password is so that an attacker can't take a list of all the hashed passwords and see if any of them match the hash of something easy like "password" or "12345". If you were to use the password itself as salt, then an attacker could calculate md5("12345".md5("12345")) and see if it matched any entries.

As I understand it, there are four levels of hashing you can use on a password table:

  1. None - store the password as plain text. If someone gets a copy of your database, they have access to all accounts. Plain text is bad, 'mkay?
  2. Hash the password - store the hash of the password, and throw away the real password. If someone gets a copy of your database, they can't see any passwords, only hashes. However, if any users have used weak passwords, then their hashes will appear in rainbow tables. For example, if a user has the password "password", then an md5 hash stored in the database would be "5f4dcc3b5aa765d61d8327deb882cf99". If I look up that hash in a rainbow table like the one at gromweb.com, it spits out "password".
  3. Use a salt value - choose a large random string like a GUID and store it in your configuration file. Append that string to every password before calculating a hash. Now the rainbow table is far less likely to work because it probably won't have an entry for "password59fJepLkm6Gu5dDV" or "picard59fJepLkm6Gu5dDV". Although precalculated rainbow tables are not as effective anymore, you can still be susceptible if the attacker knows your salt value. The attacker can calculate the hash of a weak password plus your salt and see if any user in your database uses that weak password. If you've got several thousand users, then each hash calculation lets the attacker make several thousand comparisons. How you actually use the salt may depend on the encryption algorithm you're using. For simplicity, just imagine it as appending the salt and the password together.
  4. Use a distinct salt value - now you take something distinct like the user name, e-mail address, or even user id, and combine that with the password and the large random string from your configuration file before you calculate the hash. Now an attacker who knows your salt still has to recalculate the hash for every user to see if they have used a weak password like "password".

For more details, check out the Coding Horror post, "You're probably storing passwords incorrectly".

Upvotes: 33

Bruno Rohée
Bruno Rohée

Reputation: 3534

MD5 is not secure in itself because it is partially broken (collisions) and is too small of a digest anyway. If one doesn't want to use a proper password derivation function à la bcrypt, scrypt or PBKDF2 you should at least use SHA-256 for new designs (and have a plan to migrate to SHA-3 when it will be out, so be sure to store the scheme you used to hash the password with the result, so both scheme can coexist as you use the new hashing procedure when people change passwords).

If you intend to sell your program using MD5 in any capacity can be a show stopper for most government sales (e.g. in the US algorithms used must be FIPS 140-2 approved and many other countries got the same kind of requirements).

Upvotes: 2

Decent Dabbler
Decent Dabbler

Reputation: 22783

With your solution you pretty much defeats the purpose of using a salt against precomputed dictionary attacks.

With a precomputed dictionary, as the name implies, someone has already created a table of hashes (the computed md5 result) for particular words, ahead of time.

Consider this table hashtable (with imaginary hashes, just for illustration purposes)

word | hash
------------
foo  | 54a64
bar  | 3dhc5
baz  | efef3

Testing these values against your table, could be as simple as:

SELECT h.word
FROM hashtable h, yourtable y
WHERE y.password = MD5( CONCAT( h.word, h.hash ) );

With a match, you'ld have the password.

However, if you did NOT hash the password, before concatenating it again with the password and hashing it once more, it would be more difficult to attack it with a pre-computed dictionary. Because then the password would be for instance md5( 'testtest' ) which makes the precomputed table worthless, if the precomputed table has only taken into account single instances of the word.

You can easily see that it gets even more difficult if you did not use the password as a salt, but used another random string as salt. And it gets even more difficult still, when you create unique salts for every passwords. Of course, if you create unique salts per password, you'd have to save the salt in a separate column along with the passwords in a database row.

So my advice would be:

md5( 'uniquesalt' . 'password' );

Or actually, don't use md5 at all, but use the far better sha1, sha256 (or higher) hashing algorithms.

Upvotes: 1

technomage
technomage

Reputation: 525

The reason why random password salt is recommended for hashing password, so that an attacker who knows the password hash can't compare it to rainbow table of pre-calculated hashed from dictionary.

If you're using password as salt, attacker can pre-calculate hashes of $word.md5($word) first from their dictionary

Upvotes: 1

Mat
Mat

Reputation: 206689

It doesn't do much against dictionary attacks, only twice as hard to compute a dictionary versus a single md5, and md5 is pretty cheap these days.

Upvotes: 2

Related Questions