Hash on Unicode Password
I'm writing a password salt/hash procedure for my .NET application, largely following the guide of this article: http://www.aspheute.com/english/20040105.asp
Basically the code for computing the salted hash is this:
public string ComputeSaltedHash(string password, byte[] salt) {
// Get password ASCII text as bytes:
byte[] passwordBytes = System.Text.Encoding.ASCII.GetByt开发者_StackOverflowes(password);
// Append the two arrays
byte[] toHash = new byte[passwordBytes.Length + salt.Length];
Array.Copy(passwordBytes, 0, toHash, 0, passwordBytes.Length);
Array.Copy(salt, 0, toHash, passwordBytes.Length, salt.Length);
byte[] computedHash = SHA1.Create().ComputeHash(toHash);
// Return as an ASCII string
return System.Text.Encoding.ASCII.GetString(computedHash);
}
However, I want to allow allow users to use Unicode chars in their password, if they like. (This seems like a good idea; can anyone think of a reason it's not?)
However, I don't know a ton about how Unicode works, and I'm worried if I just change both references of System.Text.Encoding.ASCII
to System.Text.Encoding.Unicode
, the hash algorithm might produce some byte combinations that don't form valid Unicode chars and the GetString call will freak out.
Is this a valid concern, or will it be OK?
You shouldn't be using any normal encoding to convert from arbitrary binary data back to a string. It's not encoded text - it's just a sequence of bytes. Don't try to interpret it as if it were "normal" text. Whether the original password contains any non-ASCII characters is irrelevant to this - your current code is broken. (I would treat the linked article with a large dose of suspicion simply on that basis.)
I would suggest:
- Use
Encoding.UTF8
to get the bytes from the password. That will allow the password to contain any unicode character.Encoding.Unicode
would be fine here too. - Use
Convert.ToBase64String
to convert from the computed hash back to text. Base64 is specifically designed to represent opaque binary data in text within the ASCII character set.
It's enough to change the first reference to Unicode
or UTF-8
. You may want to normalize the input, however, to account for various ways of entering accents and the like.
I like Jon solution better but another option is to simply store the raw hexadecimal as a string. Replace your last line with:
return BitConverter.ToString(computedHash)
One thing you may want to consider is password strengthening.
SHA1 is very fast, sometimes too fast. A system will be able to compute couple million hashes per second. The speed allows an attacker to try a common word dictionary attack (including variantions in capitalization) and number extensions. The speed of SHA1 allows a wide dictionary space to be completed in a reasonable amount of time breaking most user's passwords.
A method to strengthen the passwords is to hash it multiple times this increases the CPU requirements of the hash. Take the output of the SHA1 hash and pass it as the input for a second round. Do that at least 1000 times. This slows down hash computation for both you and an attacker. For your users it delays access by a trivial amount of time; the routine will return in 0.01 seconds instead of 0.0001 seconds. However to a brute force attack you have increased execution time by a factor of 1000.
You can roll your own but .net framework exposes a class to do just that: System.Security.Cryptography.Rfc2898DeriveBytes
RFC2898 uses SHA1 algorith and accepts plain text, salt, and number of iterations. It can output a variable length key.
http://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes.aspx
精彩评论