6 ms·
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
by timtadh 16y ago
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.]
- tptacek 16y agoFor what it's worth: this is essentially PBKDF2.
- cperciva 16y agoActually, it's essentially PBKDF1, but that's close enough for non-cryptographers.
- tptacek 16y agoShit, 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/pbkdf2.rb https://github.com/emerose/pbkdf2-ruby/blob/master/lib/pbkdf... I should stop saying PBKDF2 and just go back to saying PBKDF.
- cperciva 16y agoPBKDF2 also protects against entropy loss from repeated iteration, although that doesn't really matter unless you're using an unreasonably short hash.
- brown9-2 16y agoWhy 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.
- csytan 16y agoI can think of one reason: Lack of the available libraries on a particular platform.
- timtadh 16y agoThis is why I mentioned it. It provides improved time guarantees for your users with out needing another library.
- paperclip 16y agobcrypt 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.
- jrockway 16y agoBecause 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?
- burgerbrain 16y agoBecause we all know rolling your own in the crypto world instead of deferring to the experts never leads to catastrophe!
- brown9-2 16y agoYou 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.
- IgorPartola 16y agoWhy 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?
- Xk 16y agoIf 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.
- gcr 16y agoCould you explain a bit more about this?
- Xk 16y ago(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.
- Raphael 16y agoWhat value of X would be good for a web app? 10? 100? 1000?
- tptacek 16y ago1000 is a minimal value.
- timtadh 16y agoAs 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.