Take the 2-minute tour ×
Information Security Stack Exchange is a question and answer site for Information security professionals. It's 100% free, no registration required.

I'm afraid I'll have tomatoes thrown at me for asking this old question, but here goes.

After reading that cooking up your own password hash out of existing hashing functions is dangerous over and over again I still don't understand the logic. Here are some examples:

  • md5(md5(salt) + bcrypt(password))
  • scrypt(bcrypt(password + salt))
  • sha1(md5(scrypt(password + md5(salt))))

The typical arguments against these go as follows:

You're not a cryptographer! You've got no idea if these hashes are more secure. Leave it to the experts who know what they're doing. These add no extra security.

Granted they don't improve the function as a hash (i.e. make it harder to reverse or find collisions etc.), but surely surely they don't make it worse as a hash? If they did then hackers would be able to re-hash standardly hashed passwords into these wacky hashes as they see fit and weaken the hash? I don't buy it.

Second argument:

Kerckoffs's principle: A cryptosystem should be secure even if everything about the system is known.

Agreed. This is basically the motivation for not storing your passwords as plaintext in the first place. But if my response to the first criticism stands then these wacky hashes still function as secure hashes, and our system doesn't break Kerckoffs's principle anymore than it would with a standard hash.

Here are two possible (and worthwhile, as far as I can see) advantages to using a "wacky" hash over a normal hash:

  1. Sure, your system should be secure if the attacker has the source code, but it's a very likely possibility that your attacker wont have access to your source code and probably won't be able to guess your wacky hash, making any attempt at a brute force impossible.
  2. (This one is the real motivation behind me asking this question) BCrypt is thought to be secure, hard for the CPU and GPU (great) but can be very fast with specialized hardware. SCrypt is said to be hard to bruteforce on CPUs, GPUs and currently available specialized hardward but is more recent and not trusted by the cryptographic community as much as BCrypt due to the lack of exposure it's had. But doesn't the hash BCrypt(SCrypt(password + salt)) get the best of both worlds?

I appreciate that the passion/anger behind most rants against these home-brewed hashes comes from the average programmer's lack of knowledge of what makes a good hash, and a worry that encouraging this sort of wacky-hashing will inevitably end up with weak and useless hashes getting into production code. But If the wacky hash is carefully constructed out of solid and trusted hashes, are the gains in security not very valuable and real?


Update

I got a bunch of good answers on this, thanks. What I seemed to be overlooking in my assumptions was that, although combining hashes can't make it easier to crack the original password and therefore crack the constituent hashes, the combination of two or more secure hashes can - at least in principle - be weaker than any one of its inner hashes due to the unstudied and complex interactions between them. Meaning it could be possible to find some string that got past the wacky hash without necessarily breaking the hashes that made it up.

share|improve this question
1  
If your goal is just to protect against an attacker without access to your source code, then using a pepper (basically a hard-coded secret value that you hash along with the password) would be sufficient, and less error-prone than creating your own hash function. –  Brendan Long Apr 1 '13 at 15:10
1  
why not add a sleep(15000); after each hash? sure benefits are unknown but it certainly doesn't make it less secure :) –  ssg Apr 2 '13 at 10:55
add comment

8 Answers

up vote 32 down vote accepted

The fact that you need to ask this question is the answer itself - you do not know what is wrong with stacking these primitives, and therefore cannot possibly know what benefits or weaknesses there are.

Let's do some analysis on each of the examples you gave:

md5(md5(salt) + bcrypt(password))

I can see a few issues here. The first is that you're MD5'ing the salt. What benefit does this give? None. It adds complexity, and the salt is simply meant to be unique to prevent password collisions and pre-computation (e.g. rainbow table) attacks. Using MD5 here doesn't make any sense, and might actually weaken the scheme since MD5 has known trivial collisions. As such, there is a small possibility that introducing MD5 here might mean that two unique salts produce the same MD5 hash, resulting in an effectively duplicated salt. That's bad.

Next, you use bcrypt on the password. Ok. Well, most bcrypt implementations require a salt internally, so this is already technically invalid. Let's say you know that, and you meant to say bcrypt(md5(salt), password). This part is still falling to the weakness I described above, but it's not too shabby - remove the MD5 and it's a standard use of bcrypt.

Finally, you MD5 the whole thing. Why are you doing this? What is the purpose? What benefit does it bring? As far as I can see, there is no benefit at all. On the detriment side, it adds more complexity. Since most bcrypt implementations use the $2a$rounds$salt$hash notation, you're going to have to write code to parse that so that you can extract the hash part and store the rest separately. You're also going to need an MD5 implementation, which was unnecessary.

So, in terms of code footprint for potential attack vectors, you've gone from a simple bcrypt implementation, to a bcrypt implementation with custom parsing code, and MD5 implementation, and some glue code to stick it all together. For zero benefit, and a potential vulnerability in salt handling.

Next one:

scrypt(bcrypt(password + salt))

This one isn't too bad, but again you need some code to parse out the results of bcrypt into hash and salt / round count separately. In this case I'd guess that there is a slight benefit, because bcrypt and scrypt work in different ways for roughly the same goal, which would make it a little more difficult for an extremely well-funded attacker to build custom ASICs to break your scheme. But is that really necessary? Are you really going to hit a situation where a nation state will devote a few million dollars to just to break your hash? And, if that case ever arises, will it really bother the attacker to have to spend a few extra million to double their chip count?

Another potential issue with combining bcrypt and scrypt like this is that there has been very little study into how the two interact. As such, we don't know if there are any weird cases that can cause problems. As a more obvious example, take the one time pad. We compute c=m^k for some message m and some equally long perfectly random key k, and we get perfect security. So let's do it twice, for even more security! That gives us c=m^k^k... oh, wait, that just gives us m. So because we didn't take the time to properly understand how the internals of the system worked, we ended up with a real security vulnerability. Obviously it's more complicated in the case of KDFs, but the same principle applies.

And finally:

sha1(md5(scrypt(password + md5(salt))))

Again we're running into the MD5'ed salt issue. I'm also intrigued by MD5'ing the SHA1 hash. What possible benefit could that have, if you're already using a slow KDF like scrypt? The few nanoseconds it would take to compute those hashes pales in comparison to the hundreds of milliseconds it would take to compute the scrypt digest of the password. You're adding complexity for an absolutely irrelevant layer of "security", which is always a bad thing. Every line of code that you write is a potential vulnerability.


Now remember that point I made at the start of my answer. If, at any point in this answer, you thought "oh yeah, I didn't think about that", then my point is proven.

You're running into what I would describe as Dave's false maxim:

If I add more crypto things, it will be more secure.

This is a common trait among developers, and I once believed it too. It goes hand-in-hand with denial of other principles, such as Kerckhoff's Principle. Ultimately, you have to realise and accept that obscurity isn't a safety rail; it's a crutch for weak crypto. If your crypto is strong, it needs no crutch.

share|improve this answer
    
Nice answer. I'd not really thought about the first and the last example of stacked hash. They kinda ruined my argument a little for no reason. I was more interested in the bcrypt - scrypt hash and whether that could possibly be weaker than either scrypt/bcrypt on their own. –  George Powell Apr 1 '13 at 12:10
13  
The interaction between the two is undefined, because nobody has studied it. Combining the two might provide more security, but it also might lead to unusual interactions between the internals of those hashes. A good example of this is the one time pad. Computing c=m^k gives you a perfect level of security when k is truly random and unknown to the attacker. So let's do it twice, for more security! So we end up with c=m^k^k, which results in m. Oh, whoops. Obviously the interactions are more complex in hashes, but the principle still stands. –  Polynomial Apr 1 '13 at 13:09
2  
My point is that since we don't know what potential problems might exist, why not go with the known safe route and just use scrypt with a decent work factor? It's more than secure enough for almost every purpose. –  Polynomial Apr 1 '13 at 13:10
1  
@Polynomial +1 Great answer. However I wasn't convinced about the bcrypt/scrypt part of your answer until I read your comment about one-time pads. Might consider adding that comment to your answer. –  Phil Apr 1 '13 at 14:02
    
@Phil Done. Hopefully it makes sense. –  Polynomial Apr 1 '13 at 15:26
show 4 more comments

Crypto primitives can be stacked safely, and increase security if, and only if, you know the primitives well enough to understand their weaknesses and how those weaknesses interact. If you don't know them, or don't understand the details - well, that's how you get Dave's protocol.

The problem is very few people know them all well enough to judge if a certain combination is safe. Which is why it needs to be something that is published and reviewed, if it's not been reviewed you have no way of knowing if it's as strong as scrypt or if it's closer to CRC32.

So, if you aren't an expert - it's quite possible that you have something weaker than the weakest primitive you've used (see Dave's protocol) and you wouldn't know it. Or at least you wouldn't know it untill it's cracked – finding your users' passwords on Pastebin isn't quite the ideal way to determine the scheme is flawed.

I do agree that some degree of obscurity can help from a defense in depth perspective, but the underlying system must be secure.

Between scrypt, bcrypt and PBDKF2 - at least one of them will be supported on pretty much every platform. These are known and well tested - they offer differing levels of protection, but they are still far more secure than a bizarre stacking of md5 and sha1.

share|improve this answer
    
I think that stacking of md5 and sha1 was a bad example, I'd never use that one and it was just for illustration. I'm particulary interested in the bcrypt-scrypt stack. I still don't understand how it could possibly make the hash weaker? As I said, it's a step that could be taken by a hacker if they wanted anyway so if it does weaken the hash then that's a huge problem even if I don't do it for them! –  George Powell Apr 1 '13 at 10:37
    
@GeorgePowell : A hacker isn't only interested in recovering original plaintext passwords. He might also be interested in finding collisions. It's possible that you increase the amount of collisions by chaining those functions together. –  Christian Dec 20 '13 at 19:44
add comment

For your specific question of combining scrypt and bcrypt, remember that these functions have a configurable cost, and you want to raise that cost as much as possible, while keeping it tolerable for your specific usage. For instance, if you can use bcrypt with up to X iterations (beyond which it is too expensive for your server and your average number of user connections per second), or scrypt with up to Y iterations, then you cannot use scrypt(bcrypt) with X iterations for bcrypt then Y iterations for scrypt: this will be beyond your CPU budget.

Thus, if you cascade scrypt and bcrypt, then you must use both with less iterations than what you could have done with one alone. You don't "get the best of both worlds" by simply stringing them together. In fact, the best you can hope for is a kind of average between the two. And that comes at the price of more complex code, something which is inherently bad when talking about security (or, for that matter, maintainability).

share|improve this answer
add comment

In addition to Adam's answer, I'd like to also mention that any time you use cryptography, you should have a strong and unavoidable reason to do so. In your examples above, this does not exist.

md5(md5(salt) + bcrypt(password))
scrypt(bcrypt(password + salt))

The bcrypt and scrypt algorithms are already strong enough, and considered effectively unbreakable. What problem are you trying to solve? And why do you believe that combining their results (particularly with md5) will solve it? In the best case scenario, you've likely merely reduced the difficulty of cracking the password to that of the weakest hash, rather than actually improving security. And the worst case scenario is frighteningly undefined.

md5(sha1(md5(md5(password) + sha1(password + salt)) + password))

This solution is even worse. It manually implements a repeated hashing scheme, but without enough rounds to actually impose a significant work factor on attackers.

In a nutshell, the problem is that:

  • you're throwing around cryptography without actually having a problem that needs solving
  • you've dramatically increased the likelihood of introducing flaws in your implementation
  • you've likely decreased security to the weakest of the hashing algorithms, and
  • you've introduced an unknown worst-case scenario where none used to exist
share|improve this answer
    
The "problem that i'm trying to solve" or the advantages of doing them was addressed at the end of the question. And I still don't understand how a stack of hashes like sha1(md5(bcrypt())) could be weaker than bcrypt! Please explain, if putting md5 ontop of bcrypt made it weaker then why on earth aren't hackers doing it themselves once they get the bcryt-ed passwords? –  George Powell Apr 1 '13 at 10:57
    
@GeorgePowell Your sha1(md5(bcrypt())) scheme makes no sense. What does it offer that bcrypt alone doesn't? If you're looking for more security in bcrypt, why not just increase the work factor, or switch to scrypt? Abusing other hash primitives, especially mainly-broken ones like MD5, for an undefined and minuscule benefit (which may actually be a detriment) isn't smart and can only provide you with more complexity. –  Polynomial Apr 1 '13 at 11:06
    
I agree it makes no sense, you're right. It doesn't make a better hash. But I don't understand how it would make it weaker? Even if you'd fully beaten the md5 and sha1 hashes you'd still be left with all the strengths of bcrypt? Or am I completely wrong here? –  George Powell Apr 1 '13 at 11:12
4  
The security issue isn't really a cryptographic one. You have to write lines of code to produce this implementation, and every line you write brings potential vulnerabilities. Even subtle things like how Unicode is handled on certain machines can be catastrophic from a security perspective. Keep it simple, keep the implementation clean of needless crud, and you'll be safer. –  Polynomial Apr 1 '13 at 12:11
1  
@GeorgePowell It doesn't help them. Imagine there exists a hash function that has an output space of one single byte. It is obviously trivial for an attacker to brute force our passwords if we use that at any point to compute digests (even if we later pass that to something like bcrypt). But the fact that this weak hash exists doesn't allow an attacker to use it against us, because we're not using it. –  Stephen Touset Apr 1 '13 at 15:28
show 1 more comment

Anytime you increase the complexity of an algorithm or even add more lines of code, you increase the points of failure in your application. There may be unintended consequences of combining algorithms. This may lead to certain tells or other signs which can actually weaken the cryptographic strength of the system.

The more libraries you use in your application, the greater the risk to your application in general. If a flaw is found in one implementation that allows a weakness, code execution, etc. then your application is vulnerable. If you happened by luck to select another algorithm that was not attacked, your safe for the time being (of course you could also be on the unlucky side of luck).

Remember KISS: Keep it simple, stupid, or else you may get lost in the complexity.

share|improve this answer
add comment

Hash functions are built by cryptographers and destroyed by cryptographers. There are many strong hash functions as well as weak ones still being use today. Programmers should trust the cryptographers and the hash function. If there was ever a vulnerability in the hash function, then you would surely hear about it on the internet or through co-workers and then cryptographers will surely to a deep investigation. With any secure hash algorithm, it's only known weakness may be a bruteforce attack.

Combining hash functions adds almost no extra security and anything you might want to add, is probably already implemented in the function already.

Salting a password is great for reducing the effectiveness against rainbow tables such that a password couldn't be just "looked up." Whether you hash a function twice or change the hash function, it is essentially salting the password. And most functions include an easy method to salt so there is really no need to implement this.

Lets say I want to create my own secure hash because everyone does it. And since I'm not a cryptographer, I will need it "really" secure, because of course, every programmer knows how to make a secure hash instead of using the secure ones already created. So I create my devious hash function, mod10(md5(sha1(bcrypt(password + salt)))).

As you can see from my hash function, it is really secure because I use so many different things in it. Of course in this silly example, it is easy to see there are only going to be 10 different possible outputs. But by simply using a single secure hash function, it would have completely avoided this.

Sure, your system should be secure if the attacker has the source code, but it's a very likely possibility that your attacker wont have access to your source code and probably won't be able to guess your wacky hash, making any attempt at a brute force impossible

So we are assuming an attacker got ahold of the database table that contains the hashes. I would assume it would be very likely that an attacker can get the webpage files aswell. Your systems running these services maybe the same exploit that allowed you database to get taken. A database server is setup so that that public cannot directly access it. On the other hand, your web server containing your code, is on the front lines.

share|improve this answer
add comment

If you apply unsafe operations to a secure algorithm, you can definitely break the hashing function. Your new function could even be far worse than the weakest link.

Why don't attackers use this to break secure functions? It doesn't help them. For example, if I overwrite the first 440 bits of a password safely stored using bcrypt with zeroes, I can easily find a matching password by brute force, but that password will only work on my own terrible algorithm. A sane implementation would probably reject it.

Zeroing large chunks of a hash is clearly bad, but even safe operations can be combined into something dangerous. Adding two numbers (modulo n to ensure a constant length) is 'safe'. Generally, no entropy is lost. Still, h(x) + h(x) mod n reduces the quality of the hash h(x) by one bit, as the result is always even. The equally safe operator XOR does even worse, as h(x) XOR h(x) always returns zero.

These pitfalls are fairly obvious, but not all of them are. Keep in mind that, as always, it is trivial to invent a scheme good enough that you won't find any weaknesses yourself, but very difficult to invent one where no one else can.

share|improve this answer
    
Good answer, I think I understand the gist: wacky hashes could make it easier to find a match by brute force but couldn't make it easier to find the original password that was used. This is why hackers have no use applying the weaker hash themselves. Cracking the stacked hash does not imply cracking the inner-hashes. –  George Powell Apr 1 '13 at 11:20
add comment

I am going to disagree with a whole bunch of people who are smarter than me and more experienced in security than I am. So I am probably wrong.

Improvising your own hash function is a good idea - if you do it right. Follow these 3 simple steps.

  1. Identify a weakness or flaw in the existing hash functions.
  2. Improvise your own hash function that does not have this flaw.
  3. Verify that your improvised hash function has all the strengths of the existing hash functions.

After you complete step 3, you would be a fool not to use your improvised hash function.

share|improve this answer
3  
The problem is that you can't complete step 3. No one in the world is capable of unilaterally determining that a new hash algorithm is strong. This is why, in order to gain acceptance, new candidate algorithms go though massive multi-year refereed peer-reviewed competitions and undergo exceptional amounts of scrutiny. You simply can't know if an algorithm is safe in a vacuum. –  Xander Apr 2 '13 at 0:54
    
@Xander, I can not complete any one of the steps. So for me, I don't improvise. While step 3 is probably the most important, step 1 is the most basic. If you can't at least identify a problem with an existing secure hash function, then why are you wasting your time improvising a new hash function. –  emory Apr 3 '13 at 1:04
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.