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.]
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:
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.
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.
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.
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.
(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.
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.
[Note: you should absolutely have a different salt for each password with this approach.]