4

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

Vipresh
  • 1,250
  • 15
  • 31

1 Answers1

7
  1. Avoid using strings with crypto. Use byte arrays, i.e. byte[]
  2. 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.

poupou
  • 43,413
  • 6
  • 77
  • 174
  • 1
    Should'nt value of s be "A A" and if I get the byte array out of s it is {65,0,65} . code that I tried static void Main(string[] args) { byte[] data = new byte[] { 65, 0, 65 }; var s = System.Text.Encoding.ASCII.GetString(data); Console.WriteLine("S=" + s); byte[] newData = System.Text.Encoding.ASCII.GetBytes(s); Console.WriteLine(newData.Count()); Console.WriteLine("Printing Bytes"); newData.ToList().ForEach(byt => { Console.WriteLine(byt); } ); } Out put is S=A A 3 Printing Bytes 65 0 65 – Vipresh Apr 05 '12 at 08:55
  • I tried with Mono. Anyway the main point is that the `Encoding` classes do **not** guarantee identical roundtrips, e.g. non-printable ASCII characters. As such they are problematic for any use, like cryptography, that depends on getting identical values. – poupou Apr 05 '12 at 11:49
  • 1
    @poupou It was probably a Mono bug. Mono 3.0.2 works correctly. `echo "Console.WriteLine(System.Text.Encoding.ASCII.GetBytes(System.Text.Encoding.ASCII.GetString(new byte [] { 65, 0, 65 })).Length);" | csharp` gives 3 .Net is not fooled by `\0`. ASCII is still not a great idea, but it's not THAT bad. I think you should remove the incorrect information from your answer. – Ark-kun Nov 19 '13 at 04:05
  • 1
    Another big problem with `Encoding.ASCII` is that it silently replaces every byte above 127 with a question mark - that's about half of them. – CodesInChaos Sep 18 '14 at 15:00