Reputation: 111
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
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
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
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
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:
For more details, check out the Coding Horror post, "You're probably storing passwords incorrectly".
Upvotes: 33
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
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
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
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