I've developing a web application that will be dealing with highly sensitive information and I want to ensure the hashing of passwords is gold standard. Ideally I'd go for per-user salted SHA512 using PBKDF2 to carry out multiple iterations of the algorithm but as I'm developing in asp.net, there's no built-in .net SHA512 version for PBKDF2 - I'm limited to SHA1 (i.e. Rfc2898DeriveBytes).

My question is really, bearing in mind I'm developing in .net and am securing sensitive information, what's the best way to hash user passwords?

Should I stick with .net's SHA1 PBKDF2 implementation?
Should I use a custom implementation of PBKDF2 to incorporate SHA512?
Should I be preferring bcrypt or scrypt despite the fact that they're not widely standards accredited (e.g. NIST)?

IMHO if a PBKDF2 implementation won't let you control a critical component like the hashing function, then it's probably not worth using. The fact that it uses SHA1 makes it worthless.
–
SammitchDec 13 '12 at 23:47

@Sammitch As Polynomial says, SHA1 is fine for this purpose, though hash function agility is indeed a good thing.
–
nealmcbSep 21 '14 at 0:30

4 Answers
4

I'd go for the in-built Rfc2898DeriveBytes, with a high number of iterations - the higher the number the better, but I'd recommend 5000 as an absolute minimum. SHA1 is considered broken for some uses, but not in the way it's used in PBKDF2, and probably won't ever be within the lifetime of your product.

Quoting the NIST recommendations for PBKDF2, "A minimum iteration count of 1,000 is recommended. For especially critical keys, or for very powerful systems or systems where user-perceived performance is not critical, an iteration count of 10,000,000 may be appropriate.". As you are dealing with sensitive information you'd probably want to go with an even higher iteration count than 5000. This of course can be a usability/security tradeoff problem.
–
Henning KlevjerDec 12 '12 at 14:35

2

@HenningKlevjer Agreed, I mean 5000 as an absolute minimum. The best way to select your iteration count is to benchmark and calculate what the absolute maximum value you can get away with is.
–
PolynomialDec 12 '12 at 14:43

I wouldn't say "Microsoft provides...", it's just a third party article in MSDN Magazine. Your "SHA1 is not yet broken" sentence isn't written clearly either, since SHA1 is broken, just not as it's used in PBKDF2. I'd reformulate it to make that clearer.
–
CodesInChaosDec 12 '12 at 17:25

@CodesInChaos I'll edit, but the Microsoft thing is close enough.
–
PolynomialDec 12 '12 at 19:46

Remember that the point of the iterations in PBKDF2 is to make the password hashing slow for the attacker. Unfortunately, it makes it slow for you, too. You thus need to avoid making it unduly slow for you. In particular, consider SHA-512: SHA-512 uses a lot of 64-bit arithmetic operations. The attacker can buy PC which are good at 64-bit operations (recent enough x86 CPU have a 64-bit mode, and older processors could use SSE2 opcodes). Your server, on the other hand, could be a bit more limited: if your server runs in 32-bit mode, then SHA-512 will be quite slow for you (because you use .NET and in .NET you do not fiddle with SSE2 opcodes), giving the attacker a computing advantage (by a factor of about 6).

Using the system-provided PBKDF2 implementation maximizes the chances that you will not have such an artificial slowdown (in particular it could use a native-code implementation of SHA-1). The known shortcomings of SHA-1 do not impact its security when used in PBKDF2.

If you do anything "by yourself" then you have to understand these performance issues with all their fine details; and, if anything goes wrong, you will probably get the blame.

Personally, I would go ahead and use BCrypt. The algorithm may not be NIST-listed, but it's been around long enough (13 years now, and the Blowfish cipher itself almost 20) that I'd trust it. By comparison, MD5 was shown to be vulnerable a mere 5 years after its introduction and was considered completely broken in 18 years.

The only known attack on the Blowfish cipher in its 20 years is a known-plaintext attack, requiring an exponential number of known input messages and resulting ciphertexts (2^8n+1 where n is the number of rounds of the Feistel cipher; for standard 16-round Blowfish that's 2^129) to derive the key. For BCrypt, this same attack is infeasible; first, there's only ever one plaintext that is "valid" for BCrypt (the ASCII string "OrpheanBeholderScryDoubt"), and second, the key that would be reverse-engineered by feeding in other plaintexts is the result of the exponentially-complex key derivation function that obfuscates the password in the first place. There was a vulnerability shown in one C implementation of it for Unix systems, that broke the algorithm if it were fed a password containing non-base-ASCII characters; as a result, new algorithms that do not have the vulnerability should be prefaced with the BCrypt variant "$2y$" (though most still use the older "$2a$" which is ambiguous; the algorithm that generated the hash could be secure, but maybe not).

Now, while I prefer BCrypt, PBKDF2's no slouch. Although SHA-1 is considered vulnerable because of its constant, relatively low complexity, the estimated cost to break a single hash by renting enough CPU from a cloud provider to execute that attack would be about $2.77 million. In PBKDF2, SHA-1 is used as an HMAC, meaning SHA-1 hashing is performed at least twice per iteration of the derivation function. With 5,000 iterations and a key length of 320 bits (requiring two sets of 5,000 iterations, one for each half of the derived key), any vulnerability in breaking a single hash is offset by the fact that you'd have to reverse-engineer twenty thousand of them (adding complexity on the order of 2^14.3, making the complete attack something like 2^175).

Multiplying the $2.7 million cost to crack one SHA hash by the increased complexity gives you... a price tag of about $55.4 billion to break one PBKDF2 hash "the long way" (knowing nothing about the input password). Anyone who has that kind of scratch, and wants your secret, will find a cheaper way.

As far as i know, all mentioned key-derivation functions are ok to use. You could even use BCrypt.net, because it is the algorithm that decides over the weakness/strongness of the hash-values and not the implementation of the library. As long as the library calculates the correct values, you should be fine.