RNGCryptoServiceProvider does not produce same hash for same PWD,Salt,Iteration combination
c#, cryptography, hash, pbkdf2
Solution
- Avoid using strings with crypto. Use byte arrays, i.e. `byte[]`
- If you must use strings take great care of encodings. They will bite you 9 times out of 10;
E.g.
byte [] data = new byte [] { 65, 0, 65 };
var s = System.Text.Encoding.ASCII.GetString (data);
what is `s` value ?
Answer: "A"
If you ask the `byte[]` out of the same string you'll get: `byte [1] { 65 }`, which is not the same as the original and won't work for most cryptographic usage.
Base64 is safer since it will keep every byte intact.
Problem
Oki So I want to hash my password by appending random salt in .Net . The inbuilt classes I m using for this purpose are RNGCryptoServiceProvider - to generate the random salt and Rfc2898DeriveBytes - to hash the actual password. But when I call the GetBytes() function of Rfc2898DeriveBytes for the same combination of passwordString, SaltBytes & Iteration count the result is different. I m pasting my code for reference ``` public class PBKDF2Implementation { int minSaltSize = 32; int maxSaltSize = 64; public string CreateHash(string plainText , out string salt) { Random random = new Random(); int saltSize = random.Next(minSaltSize, maxSaltSize); // Allocate a byte array, which will hold the salt. byte[] saltBytes = new byte[saltSize]; // Initialize a random number generator. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); // Fill the salt with cryptographically strong byte values. rng.GetNonZeroBytes(saltBytes); string strSalt = System.Text.Encoding.ASCII.GetString(saltBytes); //string strSalt = System.Convert.ToBase64String(saltBytes); salt = strSalt; Console.WriteLine(saltBytes.Count()); Console.WriteLine(strSalt); Rfc2898DeriveBytes pwdGen = new Rfc2898DeriveBytes(plainText, saltBytes, 1000); // generate an RC2 key byte[] key = pwdGen.GetBytes(32); Console.WriteLine(System.Convert.ToBase64String(key)); return System.Convert.ToBase64String(key); } public bool CompareHash(string plainText, string salt, string hashValue) { byte[] saltBytes = System.Text.Encoding.ASCII.GetBytes(salt); //byte[] saltBytes = System.Convert.FromBase64String(salt); Console.WriteLine(saltBytes.Count()); Console.WriteLine(System.Text.Encoding.ASCII.GetString(saltBytes)); Rfc2898DeriveBytes pwdGen = new Rfc2898DeriveBytes(plainText, saltBytes, 1000); // generate an RC2 key byte[] key = pwdGen.GetBytes(32); Console.WriteLine(System.Convert.ToBase64String(key)); return System.Convert.ToBase64String(key) == hashValue; } } ``` And in my test class I hav this function ``` [Test] public void shouldGenerateConsistentHash() { PBKDF2Implementation cPbkdf2Implementation = new PBKDF2Implementation(); string salt; string hashValue = cPbkdf2Implementation.CreateHash("password", out salt); bool result = cPbkdf2Implementation.CompareHash("password", salt, hashValue); Assert.IsTrue(result) ; } ``` The test fails. But if in the above class PBKDF2Implementation if I replace the lines System.Text.Encoding.ASCII.GetString with System.Text.Encoding.Unicode.GetString and System.Text.Encoding.ASCII.GetBytes with System.Text.Encoding.Unicode.GetBytes The test passes. any idea why is this happening? The reason I want to make it work with ASCII encoding is because the same DB in which this hashvalue is stored will also be used by a PHP application & the hashvalue produced by PBKDF2 implementaion of PHP matches the hashvalue of Rfc2898DeriveBytes only if the salt encoding is ASCII. Here is the PHP implementation of the same http://www.php.net/manual/en/function.hash-hmac.php#101540