views:

130

answers:

2

Today I moved my web application to .net 4.0 and Forms Auth just stopped working. After several hours of digging into my SqlMembershipProvider (simplified version of built-in SqlMembershipProvider), I found that HMACSHA256 hash is not consistent. This is the encryption method:

internal string EncodePassword(string pass, int passwordFormat, string salt)
{
    if (passwordFormat == 0) // MembershipPasswordFormat.Clear
        return pass;

    byte[] bIn = Encoding.Unicode.GetBytes(pass);
    byte[] bSalt = Convert.FromBase64String(salt);
    byte[] bAll = new byte[bSalt.Length + bIn.Length];
    byte[] bRet = null;

    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
    if (passwordFormat == 1)
    { // MembershipPasswordFormat.Hashed
        HashAlgorithm s = HashAlgorithm.Create( Membership.HashAlgorithmType );
        bRet = s.ComputeHash(bAll);
    } else
    {
        bRet = EncryptPassword( bAll );
    }

    return Convert.ToBase64String(bRet);
}

Passing the same password and salt twice returns different results!!! It was working perfectly in .NET 3.5

Anyone aware of any breaking changes, or is it a known bug?

UPDATE: When I specify SHA512 as hashing algorithm, everything works fine, so I do believe it's a bug in implementation of HMACSHA256 hashing algorithm in .NET 4.0

Thanks! Andrey

+1  A: 

I believe there have been some security related changes in .net 4.0 have a look at this ...

http://www.asp.net/(S(ywiyuluxr3qb2dfva1z5lgeg))/learn/whitepapers/aspnet4/breaking-changes

The first obvious thing that sticks out is this ...

Default Hashing Algorithm Is Now HMACSHA256

ASP.NET uses both encryption and hashing algorithms to help secure data such as forms authentication cookies and view state. By default, ASP.NET 4 now uses the HMACSHA256 algorithm for hash operations on cookies and view state. Earlier versions of ASP.NET used the older HMACSHA1 algorithm.

Your applications might be affected if you run mixed ASP.NET 2.0/ASP.NET 4 environments where data such as forms authentication cookies must work across.NET Framework versions. To configure an ASP.NET 4 Web application to use the older HMACSHA1 algorithm, add the following setting in the Web.config file:

      <machineKey validation="SHA1" />

Have you explicitly set your hashing algorithm or just let asp.net decide ... if it's using a different default now it may be just grabbing any old hashing algorithm at random as the defined one is no longer supported.

Having said that, M$ may have retired the one you are using, so that may be the cause, bugger .... i just realised i need to test my CMS ... this hadn't occurred to me.

Thanks for the heads up, hopefully my thoughts will help us both !!!

Wardy
A: 

I also ran into this problem.

In my situation the end goal was to be able to set the connectionString dynamically (instead of hard-coded in the web.config). I did this by downloading the source code MS has put out for the ASP.NET Providers and changing some of the internal functionality for getting the connection string.

However, this was all for .NET 2.0 and looks just like the code Andrey posted up above. Once I got it all in place, I noticed I was unable to login to my website. So after searching I found this post. Thanks!

I went ahead and downloaded the .NET Framework 4.0 code and (if anyone wants to know) here is the new version of the EncodePassword method. I'm planning to copy this into my old version of the SqlMembershipProvider so I can use the new encryption methods and be able to login to my ASP.NET 4.0 website again!

    private string EncodePassword(string pass, int passwordFormat, string salt)
    { 
        if (passwordFormat == 0) // MembershipPasswordFormat.Clear
            return pass;

        byte[] bIn = Encoding.Unicode.GetBytes(pass); 
        byte[] bSalt = Convert.FromBase64String(salt);
        byte[] bRet = null; 

        if (passwordFormat == 1)
        { // MembershipPasswordFormat.Hashed 
            HashAlgorithm hm = GetHashAlgorithm();
            if (hm is KeyedHashAlgorithm) {
                KeyedHashAlgorithm kha = (KeyedHashAlgorithm) hm;
                if (kha.Key.Length == bSalt.Length) { 
                    kha.Key = bSalt;
                } else if (kha.Key.Length < bSalt.Length) { 
                    byte[] bKey = new byte[kha.Key.Length]; 
                    Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
                    kha.Key = bKey; 
                } else {
                    byte[] bKey = new byte[kha.Key.Length];
                    for (int iter = 0; iter < bKey.Length; ) {
                        int len = Math.Min(bSalt.Length, bKey.Length - iter); 
                        Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
                        iter += len; 
                    } 
                    kha.Key = bKey;
                } 
                bRet = kha.ComputeHash(bIn);
            }
            else {
                byte[] bAll = new byte[bSalt.Length + bIn.Length]; 
                Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length); 
                bRet = hm.ComputeHash(bAll); 
            }
        } else { 
            byte[] bAll = new byte[bSalt.Length + bIn.Length];
            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
            bRet = EncryptPassword(bAll, _LegacyPasswordCompatibilityMode); 
        }

        return Convert.ToBase64String(bRet); 
    }

Edit: Attempting to copy this one method into the old version of the SqlMembershipProvider was a bad idea. Too much has changed. :(

Jason