In general, you don't need to use more than one hashing algorithm.
What you need to do is:
Use salt: salt isn't used just to make your password more secure, it's used to aboid rainbow table attack. That way, someone will have a harder work trying to precompute the hash for passwords you store in your system.
Use multiple interations: instead of doing just SHA(password + salt), do SHA(SHA(SHA(SHA(SHA(...SHA( password + salt )))))) . Or, to represent in other way:
hash = sha(password + salt)
for i=1 , i=5000, i++ {
hash = sha(hash + salt);
}
And, finally, choose a good hashing function. SHA, MD5, etc, are not good because they are too fast. Since you want to use hash for protection, you'd better use slower hashes. Take a look at Bcrypt, PBKDF2 or Scrypt, for example.
edit: after observations, let's try to see some points (sorry, long explanation to get to the end, because it might help others searching for similar answers):
If your system is secure, like no one will ever ever get access to the stored password, you wouldn't need hash. The password would be secret, no one would get it.
But no one can assure that the database with the passwords will be stolen. Steal the database, got all the passwords. Ok, your system and your company will suffer all the consequences of it. So, we could try to avoid this password leaking.
NOTICE that we are not worried about online attacks in this point. For one online attack, the best solution is to slow down after bad passwords, lock the account after some tries, etc. And for that it doesn't matter which way you encrypt, hash, store, etc, your password. Online attack is a matter of slowing down the password inputs.
So, back to the don't let them take my plain passwords
problem. The answer is simple: don't store them as plain text. Ok, got it.
How to avoid that?
Encrypt the password (?). But, as you know, if you encrypt it, you can decrypt it back, if you have the proper key. And you'll end up with the problem of "where to hide" the key. Hum, no good, since they got you database, they can get your key. Ok, let's not use it.
So, another approach: let's transform the password in something else that can't be reversed and store it. And to verify if the supplied password is correct, we do the same process again and check if the two tranformed values match. If they match = the good password was supplied.
Ok, so far so good. Let's use some MD5 hash in the password. But... if someone has our stored hashed value of password, he can have a lot of computer power to calculate the MD5 hash of every possible password (brute force), so he can find the original password. Or, even worst, he can store all the MD5 from all characteres combinations, and easily find the password. So, do a lot of iteractions, the HASH(HASH(HASH())) thing, to make it harder, because it'll take more time.
But even that can be circunvented, the rainbow table was created exactly to speed up against this kind of protection.
So, let's use some salt over it. This way, at each interaction, the salt is used again. One trying to attack your passwords will have to generate the rainbow table considering that the salt is added each time. And when he generates that rainbow table, since it was generated with one salt, he'll have to calculate again with the other salt, so he will have to spend some time for each password (=each salt). Salt won't add "more complexity" to the password, it'll just make the attacker loose time generating the rainbow table, if you use one salt for each password, the table from one salt is useless to another password.
And using more than one hash will have helped here? No. The person generating a specific rainbow attack will be able to generate it using one or more hashes, anyway.
And using more than one hash can lead you to one problem: it's as secure as the weakest hash you use. If someone find collisions in one hash algorithm, it's that hash that will be exploited, at any point of the the iteration process, to break the password. So, you don't gain anything by using more hashes algorithms, it's better to choose just one good algo. and use it. And if yuo ever hear that it has been broken, think how you'll change it in your application.
And why use bcrypt or something like that (you say you use it): because the attacker will have to spend more time generating the tables. That's why using MD5 + wait (3 seconds) doesn't help: the attack will be offline, anyway, so the attacker can generate the tables without the (3 seconds delay).
MD5(password)
. We said it's not secure, so they suggested usingMD5(MD5(password))
instead...