Your assumption that such hashes have to be cracked one layer at a time MUST be based on an presumption that an attacker will have no knowledge of your algorithm (your choice of which hash functions are used and how they are chained, etc) and will be unable to determine it. In theory and based on this presumption: In terms of an attacker using lookup tables, when they lookup a hash they would find that the result is a string that's not the password itself but instead the hash from the parent hashing function. They would then need to look that up, and so on until they eventually get to the actual password. There is an inefficiency of having to do multiple lookups, but more crucially for this to actually work in practice, in order to actually be able to perform these lookups, the attacker would require lookup tables that cover the hashes of all possible hashes. Such tables cannot exist because they would require a vast amount of disk space to store. Only two chained hash functions could therefore actually suffice here to block lookup attacks.
You cannot rely upon this presumption though. If we assume that an attacker does somehow know the algorithm, then with a lookup table based attack, the attacker can compile lookup tables based on your specific algorithm prior to then attempting an attack on the stolen hashes. Understand that hashing algorithms are designed to be quick, so throwing in a few extra function calls isn't really going to significantly impact an attacker generating them. Furthermore in relation to brute-force/dictionary based attacks against an offline copy of the hashes, the impact of your algorithm in terms of speed of execution is going to be negligible.
Xinox and Asik also raised some excellent additional points earlier.
Just want to correct myself on a point I made here in regards to hashes of hashes in lookup tables and iterative lookups. MD5 produces a 128-bit output so there are 2^128 (3.40282366920938463463374607431768211456 × 10^38) possible permutations, which would obviously require a vast amount of storage. However obviously you don't actually need a lookup table containing ever single possible hash of a hash. Underlying an attack you've got a much smaller set of password guesses and you'd only need hashes derived from these. So if for example you have one million possible passwords in your lookup table, and you're attacking a set of hashed passwords that have been put through three chained executions of md5() i.e. md5(md5(md5(password))), then for each password guess entry you'd also need to include in the lookup table the result of md5(md5(guess)) and md5(md5(md5(guess))) in order to do the complete iterative lookup, i.e. just three million entries. This requires far less disk space than I was originally considering and therefore makes an iterative lookup approach much more viable.
Though obviously as originally described, the attacker will most likely know your algorithm and so could instead create new lookup tables specific to it, trading some degree of processing time for a lower storage requirement.
I don't see the multiple hashes making it less secure, but it doesn't really make it more secure either since while you're running multiple iterations of the hash function, they're "fast hashes", so you're not increasing the cost by a large extent. Something like bcrypt follows a similar design (where it iterates over the input multiple times), but it does it to increase the computational complexity of the entire hash computation (That is, it's designed to take a certain amount of time per hash, not a certain amount of iterations)
Precisely, there is merit to the basic principle of iterative hashing because it increases the time required to perform an attack. However as you mention, hashing functions are designed to be fast and so chaining a few together is going to make little difference. PBKDF2 and bcrypt perform iterative hashing in a similar fashion, however, the algorithm is designed to be significantly slower, thousands of iterations are usually done, and the algorithm by which hashing is chained has been designed by experts in the field of cryptography. Use of one of these two hashing functions can have a significant impact on an attacker.
Regarding salts, instead of using just one salt for all accounts (I've seen that suggested/used before), use a random salt per user (like their username). Even without something like bcrypt that would defeat rainbow tables in the case where the attacker is trying multiple accounts (While if the attacker is just targeting one account, like an admin, it won't help at all, that's where bcrypt comes in)
Per-user salts is indeed the right way to use them. A single salt for all users invalidates basic lookup/rainbow tables requiring an attacker to generate a new set that incorporates the salt within the computation, with which hashes for all users can then be attacked. A per-user salt requires an attacker to generate new lookup/rainbow tables for each user individually, which further increases time and data storage requirements. Furthermore per-user salts help ensure that all hashes are unique, therefore if two users shared the same password the attacker would be unaware of this. Similarly if performing a brute-force/dictionary attack on an offline copy of hashes, each computation of a guess will only be valid for a single user record with per-user salts, which is particularly significant when combined with PBKDF2/bcrypt.
To be clear, salting does NOT "defeat" rainbow tables in any case, it simply requires new ones be generated that incorporate the salt in the computation.
Using usernames for salts isn't a great idea. Usernames are reused and may be publicly known, reducing lookup-table storage requirements for an attacker and allowing them to pre-compute lookup tables in advance of breaching a database. With random salts, which an attacker should presumably only gain access to at the same time as the hashes, i.e. upon breaching the database, lookup-tables must be generated at that point and passwords cracked before the breach is discovered and passwords changed. Using the username as a salt isn't terrible, but its not as good as a proper random salt.
I disagree with the "good rule of thumb" in the article some people are pointing to
that suggests that salts should be as long as the output of the hash function. As they describe under the heading 'short salt', with a very short salt an attacker may have no problem generating lookup/rainbow tables covering all possible permutations of the salt, which I agree with, however recommending a salt length of 128bits in the case of md5 is just ridiculous in my opinion. It should be assumed of course that salts are available to the attacker.
So for anyone that says that double or triple or n+ hashing doesn't make it secure, put your money where your mouth is;
MD5 hash of an MD5 hash of a simple password.
And if in 7 days you can't produce either the original MD5 nor the original text, we'll just assume like previously predicted; you're wrong.
I personally do follow your train of thought, you are assuming that an attacker would have to lookup the hash in a table that contains all hashes of hashes, repeating this as many times as necessary (depending on how many times hashing functions were chained), eventually ending up at the first hash, at which point they would be trying to lookup the actual password that resulted in that first hash. You are also assuming that the lookup table needs to include all possible hashes of hashes and therefore arguing that this would be impossible because the lookup tables would be too vast to exist. However
, you are completely misunderstanding how someone would go about attacking such a hashed password, as others are trying to explain to you. An attacker, knowing your hashing method - two chained executions of md5() - would simply generate a new
lookup table of password guesses in which would be stored the resulting hash from your hashing mechanism against each possible password. The attacker would then lookup your hashes in this new lookup table.
To be really clear, let's say that an attacker wants to have 'hello' as an option in their lookup table and they are attacking a set of hashed passwords that have been hashed with two chained executions of md5(), i.e. md5(md5(password)). md5('hello') = 5d41402abc4b2a76b9719d911017c592 and md5(md5('hello')) = 69a329523ce1ec88bf63061863d9cb14. Based on your logic the attacker would use a lookup table based on single executions of md5() which would need to include the following entries:
5d41402abc4b2a76b9719d911017c592 -> hello
69a329523ce1ec88bf63061863d9cb14 -> 5d41402abc4b2a76b9719d911017c592
In actuality the attacker would build a new lookup table based on md5(md5(password) which does not need any hashes of hashes:
69a329523ce1ec88bf63061863d9cb14 -> hello
Furthermore I've explained at the top of this post why you don't actually need ALL hashes of hashes but just a subset.
Why don't you learn to ****ing read?
Using a salt means the salt is known, include salt in the cracking system.
Salts do not slow down cracking or stop dictionary/bruteforce attacks from working, all they do is stop rainbow tables.
An md5 hash of an md5 hash also stops rainbow table attacks because THERE ARE NO RAINBOW TABLES OF THE MD5 HASHES, WILL YOU PLEASE RE-READ UNTIL YOU FINALLY UNDERSTAND?
tldr; MD5'ing an MD5 stops rainbow table attacks like salting does, neither method stops or slows down dictionary/bruteforce attacks.
Salting does NOT stop lookup/rainbow tables, it simply requires new ones be generated that incorporate the salt in the computation.
Chained hashing also does NOT stop lookup/rainbow table type attacks, it simply requires that new tables be generated where hashes are computed using the same algorithm as used on the hashes to be attacked. Tables containing hashes of hashes are not necessary.
Perhaps it'd also help if you'd turn down the rage dial a little.
You do realise that the md5 hashes have a small number of characters and a fixed length. The original MD5 (the hash of the password) is trivial to find because you know its exact length and what characters are in it!
Right, sha1(md5(password)) is... well, it's silly.
Adding salt prevents the generation of rainbow tables, adding iterations prevents (or at least, makes less feasible) brute force attacks. Both of these ideas have been used in modern encryption algorithms, there's not a good reason to try and cobble together something using md5 hashes.
The real concern is, if someone gets access to your entire database, what information to they now have? That's what often gets overlooked by sites.
Again, salting does NOT stop lookup/rainbow tables, it simply requires new ones be generated that incorporate the salt in the computation.
Adding iterations does NOT prevent brute force attacks, it only slows them down to some degree.
Yeah, 32 characters long and it contains 36 possible letters per character, that's 6.334028666×10⁴⁹ combinations.
Assuming you can check 1 million hashes per second, that's 6.334028666×10⁴³ seconds or 1.055671444×10⁴² minutes or 1.759452407×10⁴⁰ hours or 7.331051697×10³⁸ days or 2.008507314×10³⁶ years, etc. to check every possible combination.
So, the double MD5 is still up on the other page if anyone's able to prove that double hashing is weak.
I don't know where you're getting 36 characters from. An MD5 hash is hexadecimal, i.e. it consists of characters 0-9 and a-f, in other words 16 possible characters. That's 16^32 = 3.40282366920938463463374607431768211456 × 10^38 possible permutations. I'm not going to bother going into the rest of it, check the first thing I wrote in this post for an explanation of why you don't need to iterate through all hashes of hashes.
Wow guys - seems like there are lots of mixed opinions on this one!
I've never looked into Salting passwords before, so start implementing salts now.
From what I understand, a Salt is a 'unique' phrase appended onto the end of a password, e.g.
So what if I still multiple hashed that?
Or what if I added a different salt everytime?
Replace the word 'phrase' with 'string', and it doesn't have to be appended, you can prepend it or even chop it and the password up and mash them together in some way. You should always assume that an attacker has access to your code though, so doing something fancy i.e. mashing the two together in some way is pointless. Just append/prepend it, your choice as to which.
Both of your suggested hash-chaining options are useless, they offer no real benefit whatsoever over simply adding a single salt to a single use of a hash function. Multiple salts make no difference, you need to assume the attacker has them along with the hash after all.
- Stop trying to come up with odd hashing algorithms like this, they're not going to help you at all.
- Stop using md5/sha1, at least use sha256 or preferably in some cases PBKDF2/bcrypt.
- Do use a salt and use a different one for each user.