Hacker News new | ask | show | jobs
by dabeledo 1616 days ago
> simply do bcrypt(md5(password))

This could also be problematic.

Password Shucking https://www.youtube.com/watch?v=OQD3qDYMyYQ

5 comments

Ok, some text with a 3 lines explanation of the attack, instead of a 45 minutes video where it's explained somewhere in less than a second:

https://security.stackexchange.com/questions/234794/is-bcryp...

It can be a good migration strategy. Just make sure to fix it in first next Login of a user.

Still much better to have md5 directly in your db.

I fixed something like this just 4 years ago. :|

Honestly, not as a big a deal as some people make it.

First off, you'd have to assume the attacker knows the bcrypt hashes are bcrypt(md5(password)) – an attacker wouldn't always know this

Also it assumes there is password reuse, but that the password is strong enough that the md5 is uncracked.

I'm also not quite sure about the circumstances where that would be relevant, but on the StackExchange a sibling comment found there is this further explanation:

> > I know I'm probably stupid but... how is this different from a dictionary attack? Instead of trying a list of known passwords, you try their md5s. If the md5 hasn't been cracked before, chances are that the password is strong enough to resist being cracked now. – nobody Jul 17 '20 at 14:55

> Because there are plenty of MD5s in the wild that A) just happen to not have been cracked yet because they weren't interesting enough to stand out, but B) once an attacker can figure out that that MD5 is inside a really interesting, high-value-target bcrypt, they might spend a lot more effort to crack that MD5. So it's not just a dictionary attack; it's a dictionary attack of passwords that are currently unknown but might be crackable with additional effort. And that effort is much less than trying to crack that password if it was only inside a pure bcrypt. – Royce Williams Jul 17 '20 at 15:00

https://security.stackexchange.com/questions/234794/is-bcryp...

So the assumption is: There is a breach A of an low-interest target with MD5 hashes and a breach B of a high-interest target with BCrypt(MD5) hashes. As A is not interesting enough, people don't invest the time to crack A's MD5s. But as B is super interesting they will use A as a dictionary source to then know on which MD5s they should invest a high amount of time, as it will help them crack the high-interest target B. Note that no specific user association takes place, like in the presentation about password shucking by Sam Croley (above Youtube link), where usernames/emails of A and B are correlated.

I think this is a bit more plausible than Croley's take on it. Because if I have identified a high interest individual, I would already invest a lot time to crack the MD5 password.

And yes, what you said bears repeating: All of this attack lives in the small space where the password is too strong to be cracked from a simple MD5 hash when you are mildly interested but not strong enough to prevent cracking when you are deeply interested – for varying degrees of mildly and deeply interested. Overall I would like to read about real world examples where this made the difference and how that password happened to fall into that region.

Thanks for sharing this, TIL.

It's a very interesting attack, highly specific to the high-number of breaches, high password reuse environment we're in that enables at-scale password cracking.

I don't think it invalidates this advice completely. You should watch the talk and eventually add a global pepper (assuming it does not leak), and of course do the final bcrypt(md5(pass)) -> bcrypt(pass) migration upon user login.

I wonder if adding a random salt for each account would help in that situation?.

bcrypt(md5(password) + salt) + salt

the problem with password shucking would be that they just do a bcrypt(md5) over the list of md5 hashes they have and check if they exist in your database.

but if each hash is salted they would need to run every their complete md5 hash list through bcrypt for each account instead of once per database.

No that wouldn't help because you'd need to store the random salt along the hashes. So you can still do: bcrypt(other_leaked_md5 + salt) to shuck it (i.e confirm if both are the same, and skip the bcrypt on your stronger service). The defense is a global "pepper" that would be in the code but not the DB, assuming only the second one leaks.