Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drop in replacement for FormsAuthentication.HashPasswordForStoringInConfigFile?

This is a solution for SHA1 variant.

     public static string GetSwcSHA1(string value)
     {
        SHA1 algorithm = SHA1.Create();
        byte[] data = algorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
        string sh1 = "";
        for (int i = 0; i < data.Length; i++)
        {
            sh1 += data[i].ToString("x2").ToUpperInvariant();
        }
        return sh1;
     }

For MD5 you only need to change the algorithm to:

MD5 algorithm = MD5.Create();

Hope you don't mind, just going to add a VB.NET variant of your code above:

    Public Shared Function CreateHash(saltAndPassword) As String
        Dim Algorithm As SHA1 = SHA1.Create()
        Dim Data As Byte() = Algorithm.ComputeHash(Encoding.UTF8.GetBytes(saltAndPassword))
        Dim Hashed As String = ""

        For i As Integer = 0 To Data.Length - 1
            Hashed &= Data(i).ToString("x2").ToUpperInvariant()
        Next

        Return Hashed
    End Function

Whats the recommended way forward for issues like this? Continuing to use "obsolete" calls is obviously not the suggested path, so has it been replaced by something else other than "just use the membership APIs"?

The best way (which you've ruled out) purely within the .NET Framework is to change everything over to have the passwords hashed by PBKDF2, Bcrypt, or Scrypt. PBKDF2 is provided in .NET by the Rfc2898DeriveBytes Class.

The second best way is to end up with two "versions" of passwords:

  • Version 0 would have been the old HashPasswordForStoringInConfigFile, but you can update those in bulk, offline, to Version 1 and remove those old hashes entire before anyone steals them and you end up in the trade news as having pathetic 90's vintage password hashing.
  • Version 1, which is the PBKDF2 of the existing HashPasswordForStoringInConfigFile value! I.e. you take your current nasty old hashes, and PBKDF2 them with a new random salt and a high number of iterations, and store the result. Then, when a user wants to log in, you feed their password to the code @RichardBažant wrote, so you have what HashPasswordForStoringInConfigFile would have returned, and then you apply PBKDF2 to that result!
    • i.e. it's actually Rfc2898DeriveBytes(HashPasswordForStoringInConfigFile(password)),PerUserSalt,YourIterations)
  • Version 2, which users who have Version 1 hashes are upgraded to. All the columns except "version" are the same, but after you compute and validate Version 1, then you calculate Rfc2898DeriveBytes(password),PerUserSalt,YourIterations) and replace the version 1 hash with the version 2 hash (and change version to 2, of course).

The third best way is the second best way, but with only version 1. Be careful, this way lies DCC2 madness - you keep wrapping your old output inside newer algorithms

In both cases, you'll be storing PBKDF2-HMAC-SHA-1 results in the database, so you'll need:

  • Password hash (BINARY(20)) - i.e. the PBKDF2 output.
  • Your salt (BINARY(16), generated per-user by RNGCryptoServiceProvider Class)
  • Optional: Number of PBKDF2 iterations (INT, start in the tens of thousands and go up to just before your server will be CPU bound at max load; increase as your hardware is upgraded)
    • This allows you to transparently increase your security level every time a user enters their (correct) password.by first validating it's correct, then re-hashing it with a higher iteration count
  • Optional: A "version" (TINYINT) so that upgrading to another algorithm later is easier, since you can have more than one version active at once.

P.S. for the Version 1 or Version 2 newer algorith, Jither created a .NET library capable of PBKDF2-HMAC-SHA256, PBKDF2-HMAC-SHA512, and so on; my Github repository contains a variant of it with a reasonable set of test vectors.


Why cannot use the simplest one by .Net

public static string HashString(string inputString, string hashName)
{
  var algorithm = HashAlgorithm.Create(hashName);
  if (algorithm == null)
     throw new ArgumentException("Unrecognized hash name", hashName);

  byte[] hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
  return Convert.ToBase64String(hash);
}

Richard's answer worked for me great. This is the code de-compiled from .NET Framework 4.5. If anyone thing is it better please use it. I guess it may be a bit faster.

        public static string BinaryToHex(byte[] data)
        {
            if (data == null)
            {
                return null;
            }
            char[] hex = new char[checked((int)data.Length * 2)];
            for (int i = 0; i < (int)data.Length; i++)
            {
                byte num = data[i];
                hex[2 * i] = NibbleToHex((byte)(num >> 4));
                hex[2 * i + 1] = NibbleToHex((byte)(num & 15));
            }
            return new string(hex);
        }

        private static char NibbleToHex(byte nibble)
        {
            int aChar = (nibble < 10 ? nibble + 48 : nibble - 10 + 65);
            return (char)aChar;
        }