The basic approach is
Hash(secret | salt), which is stored along with the salt. Obviously it's important to use a modern hash algorithm like one of the SHA2 variants (though currently SHA1 is not totally broken like MD5 is). Note that if you are storing challenge questions this way, you probably want to
ToLower() the secret before hashing it. This may reduce the complexity somewhat, but security questions aren't intended to have high entropy like a password anyway.
What about brute-force attacks like Rainbow tables?
Concerns about brute-force attacks can be mitigated in two ways:
- Require a minimum amount of entropy in the secret (length, complexity, etc.).
- Increase the amount of computation required to perform the hash.
The method used in
bcrypt is a good improvement on standard hashing because it increases the amount of time to calculate the hash by many orders of magnitude. Basically, it just iteratively hashes the secret many times:
Hash(Hash(...Hash(secret | salt)...))
Because the amount of time is still relatively small (hundreds of milliseconds), it does not add an intolerable delay to the legitimate user. But it can ruin automated brute-force attacks.
That's not good enough
If the additional difficulty of
bcrypt is insufficient, try
scrypt. It works like
bcrypt but needs to save each iterative hash in memory for a final merge at the end. This adds the same time complexity as
bcrypt but also increases space complexity, making it more difficult to implement in hardware, especially in parallel.
Honestly, I'm not aware of a C# library to implement this, let alone one "certified" by some official body, but it wouldn't be too difficult. Assuming you write thread-safe code, and the password verification takes place on a trusted system like a server, implementation concerns like side-channel attacks are minimized.
Further reading: This answer to Is BCrypt a good encryption algorithm to use in C#? Where can I find it?