Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

B-crypt and S-crypt are great libraries to use to solve this problem. However, the poor man's approach is as follows with HASH being your favorite hash function

    h = HASH.new()
    HASH.update(password)
    HASH.update(salt)
    for x in xrange(X):
        HASH.update(HASH.digest())
    return HASH.digest()
this approach "strengths" the hash by forcing you to calculate it over and over again. You should set X to be the number of rounds you want to conduct. Ie. how slow you want you server to respond to an individual request. It is always a trade-off between server slowness for individual requests and "security" of the hash function. The goal is to make dictionary attacks take longer than is feasible for you attackers to conduct.

[Note: you should absolutely have a different salt for each password with this approach.]



For what it's worth: this is essentially PBKDF2.


Actually, it's essentially PBKDF1, but that's close enough for non-cryptographers.


Shit, you're right. (PBKDF2 generates arbitrary-length output, which is what you want from a key derivation function, but maybe not something you care about for a password hash).

Here's a version that's much easier to read than the spec:

https://github.com/emerose/pbkdf2-ruby/blob/master/lib/pbkdf...

I should stop saying PBKDF2 and just go back to saying PBKDF.


PBKDF2 also protects against entropy loss from repeated iteration, although that doesn't really matter unless you're using an unreasonably short hash.


Why would you use this "poor man's approach" over bcrypt or scrypt? My understanding is that these two work on a very similar concept (work factor) and are free to use.


Some projects require FIPS 140-2 compliance. I've not been able to find that blowfish or bcrypt are certified. See http://csrc.nist.gov/publications/fips/fips140-2/fips1402ann...


If a randomly clobbered together and unvetted system is compliant, but bcrypt isn't, that just goes to show how little FIPS140-2 compliance actually means. (as if everybody didn't already know it's worthless)


Nonetheless, some projects mandate use of FIPS 140-2 hashing algorithms, and afaik bcrypt is not one. So if you find yourself on such a project, bcrypt is not an option on the table.

I'd be happy to find out I'm wrong.


I can think of one reason: Lack of the available libraries on a particular platform.


This is why I mentioned it. It provides improved time guarantees for your users with out needing another library.


bcrypt is open source and ported to a range of operating systems. I cannot imagine getting it up and running on any system would be too difficult. It would probably be wiser to to spend the time to get bcrypt (or another standard scheme) working rather than coming up with some custom scheme which probably hasn't had the same level of thought put into it.


Because building your own square-shaped wheel is more fun than talking a walk over to your friendly neighborhood wheel store and buying a round one?


Because we all know rolling your own in the crypto world instead of deferring to the experts never leads to catastrophe!


You assume those are the only two choices, I was considering that in some environments these might be the only two choices when I asked the question.


Why not also add

  HASH.update(salt)
On each iteration as well? Then again, if things were this simple, someone would have already said "just do this", it would have been peer-reviewed and would have become widely used. Anyone know why this hasn't caught on yet in web frameworks like Django/Rails?


If you're talking about why you don't do hash.update(salt) on every round, well it turns out that H(salt || H(H(H(H(H(H(password))))))) is just as strong as H(salt || H(salt || H(salt || H(salt || H(salt || H(salt || H(salt || password))))))). So you don't add any security by doing that.

When working with cryptography, "why not also add" is really dangerous, because some times you get less secure systems after doing so. And other times you get no benefits from doing so.


Could you explain a bit more about this?


(Aside: I'm sure that tptacek could give a significantly better answer here than I can, but I'll give it a shot. (And then whenever he answers trust him more than me.))

Assume you have a perfect cryptographic hash function H(X). No matter how many of N bits of X you change (0 < N <= len(X)), on average 50% of the bits of H(X) will change. So, let's consider the case of just H(salt || H(salt || password)) versus H(salt || H(password)).

Let A = H(salt||password), and let B = H(password). A and B are now, for all practical purposes, two different random integers. Each of which has the same entropy. This is because adding the salt should make no impact on the quality of these random numbers. It should now be fairly easy to see that there is no difference between H(salt||A) and H(salt||B), other than the fact that they produce different outputs.

This is all based on the assumption that the hash function is a perfect one -- however this assumption is reasonable for strong hash functions.


What value of X would be good for a web app? 10? 100? 1000?


1000 is a minimal value.


As I said, it depends on how slow you want your server to respond to a given request. For instance, you could set X so high that it takes a minimum of 1 second to respond to a request. That is probably not necessary. The real answer is you need to take the estimated minimum amount of time to calculate one of hash using the function you have chosen. Then compute using that number as the basis the minimum number of rounds to assure reasonable security for a single password. Reasonable security could be it would take 1 year to a do a full dictionary attack alphanumeric only or perhaps 1 month or 1 week. The value you choose for the time it takes to brute force one password is the level of protection you are providing your users.

EDIT: also what Thomas said. It is in the standard that 1000 should be the minimum value. I would use higher based on the level of protection you want to give your users. Note that different hash functions have different costs so this will also impact the choice of X.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: