What is default hash algorithm that ASP.NET membership uses?
Asked Answered
S

7

63

What is default hash algorithm that ASP.NET membership uses? And how can I change it?

Suazo answered 16/7, 2009 at 12:51 Comment(0)
M
51

EDIT: Do not use the Membership Provider as-is because it is horridly inadequate in terms of protecting user's passwords

In light of the fact that googling "membership provider hashing algorithm" turns up this answer as the first result, and the gospel that will be inferred, it behoves me to warn folks about using the Membership Provider like this and using hashes like SHA-1, MD5 etc to obfuscate passwords in databases.

tl;dr

Use a key-derivation function like bcrypt, scrypt or (if you need FIPS compliance) PBKDF2 with a work factor sufficient to necessitate the hashing time for a single password to be as close to 1000ms or more.

Hashes are easy to brute force these days with ample examples of data breaches in recent history. To prevent your user's passwords from ending up on pastebin in the next hack, ensure that passwords are hashed with a function that takes a sufficiently long time to compute!

Instead of Membership Provider, try IdentityReboot or the newer implementations from Microsoft that Troy Hunt talks about at the least.

It's also interesting that on the same google results mentioned above I find a tutorial showing folks preciously how easy it is to brute force these password hashes using popular tools like JtR or Hashcat. On a custom GPU rig, SHA1 can be cracked at a staggering rate of 48867 million hashes per second! With a free dictionary like rockyou or the like, a motivated person with your database will very quickly have most of your users passwords. As a developer, it's your ethical responsibility to do what is necessary to protect the security of your users' passwords.


The default hashing is SHA1 but they also salt it and base64 it:

public string EncodePassword(string pass, string salt)
{
    byte[] bytes = Encoding.Unicode.GetBytes(pass);
    byte[] src = Encoding.Unicode.GetBytes(salt);
    byte[] dst = new byte[src.Length + bytes.Length];
    Buffer.BlockCopy(src, 0, dst, 0, src.Length);
    Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
    HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
    byte[] inArray = algorithm.ComputeHash(dst);
    return Convert.ToBase64String(inArray);
}

If you want to know more about how to change it I still need to find out (unless using custom provider see below) however SHA-1 is pretty good for now. If you are looking to reverse it or lookup from this these guys did some work on that: http://forums.asp.net/p/1336657/2899172.aspx

This SO question will help in reversing or duplicating this technique if that is what might be needed. Reimplement ASP.NET Membership and User Password Hashing in Ruby

If you are making a custom provider you can create your hashing and encryption algorithms and methods.

private byte[] ConvertPasswordForStorage(string Password)
      {
         System.Text.UnicodeEncoding ue = 
      new System.Text.UnicodeEncoding();
         byte[] uePassword = ue.GetBytes(Password);
         byte[] RetVal = null;
         switch (_PasswordFormat)
         {
            case MembershipPasswordFormat.Clear:
               RetVal = uePassword;
               break;
            case MembershipPasswordFormat.Hashed:

               HMACSHA1 SHA1KeyedHasher = new HMACSHA1();
               SHA1KeyedHasher.Key = _ValidationKey;
               RetVal = SHA1KeyedHasher.ComputeHash(uePassword);
               break;
            case MembershipPasswordFormat.Encrypted:
               TripleDESCryptoServiceProvider tripleDes = new 
       TripleDESCryptoServiceProvider();
               tripleDes.Key = _DecryptionKey;
               tripleDes.IV = new byte[8];
               MemoryStream mStreamEnc = new MemoryStream();
               CryptoStream cryptoStream = new CryptoStream(mStreamEnc, 
        tripleDes.CreateEncryptor(), 
      CryptoStreamMode.Write);

               cryptoStream.Write(uePassword, 0, uePassword.Length);
               cryptoStream.FlushFinalBlock();
               RetVal = mStreamEnc.ToArray();
               cryptoStream.Close();
               break;

         }
         return RetVal;
      }

private string GetHumanReadablePassword(byte[] StoredPassword)
      {
         System.Text.UnicodeEncoding ue = new System.Text.UnicodeEncoding();
         string RetVal = null;
         switch (_PasswordFormat)
         {
            case MembershipPasswordFormat.Clear:
               RetVal = ue.GetString(StoredPassword);
               break;
            case MembershipPasswordFormat.Hashed:
               throw new ApplicationException(
        "Password cannot be recovered from a hashed format");

            case MembershipPasswordFormat.Encrypted:
               TripleDESCryptoServiceProvider tripleDes = 
        new TripleDESCryptoServiceProvider();
               tripleDes.Key = _DecryptionKey;
               tripleDes.IV = new byte[8];
               CryptoStream cryptoStream = 
        new CryptoStream(new MemoryStream(StoredPassword), 
      tripleDes.CreateDecryptor(), CryptoStreamMode.Read);
               MemoryStream msPasswordDec = new MemoryStream();
               int BytesRead = 0;
               byte[] Buffer = new byte[32];
               while ((BytesRead = cryptoStream.Read(Buffer, 0, 32)) > 0)
               {
                  msPasswordDec.Write(Buffer, 0, BytesRead);

               }
               cryptoStream.Close();

               RetVal = ue.GetString(msPasswordDec.ToArray());
               msPasswordDec.Close();
               break;
         }
         return RetVal;
      }

http://msdn.microsoft.com/en-us/library/aa479048.aspx

Moses answered 16/7, 2009 at 12:58 Comment(7)
The EncodePassword function here doesn't work. See a working example from "Rawbert" below.Umbria
I take serious issue with the line "SHA-1 is pretty good for now" in the context of the popular brute force techniques and the speed at which SHA-1 hashes (and mutations thereof) can be cracked and that this is the accepted answer so many folks are going to assign trust to it.Stockmon
Probably easier just to put that this was the solution in 2009 when the question was answered rather than the verbose response as most devs I hope know this nowadays. I believe most good devs will check dates of solutions and probably only use this if working on some legacy system that is still running insufficient hashing today. In 2009 SHA-1 was "pretty good for now", that "for now" has passed.Moses
@RyanChristensen is completely right. Nowadays ASP.Net Identity uses pbkdf2 with 10k iterations (SHA256/128-bit salt/256-bit subey) which is pretty current.Pretense
Hi @Pretense - If I look at referencesource.microsoft.com/mscorlib/a.html#2f0361c615528d06 it still looks like HMACSHA1 is used, not SHA256. What am I missing out?Clevie
@Clevie Apologies, I should have said that it's version 3 of aspnet Identity, so is less helpful perhaps... github.com/aspnet/Identity/blob/dev/src/…Pretense
I suggest that you use Hash as function name as the function hashes a string and encoding and hashing is two very different concepts. Also I recommend to rename the variable "pass" to "value" as hashing does not only apply to passwords (many times hashing is used on for example emails, or ssno to anonymize personal data)Dislimn
E
41

The above answer by Ryan Christensen isn't complete. The part where it converts the salt to a byte[] isn't correct.

This is a working example that I've implemented in a solution for a client:

public string Hash(string value, string salt)
    {
        byte[] bytes = Encoding.Unicode.GetBytes(value);
        byte[] src = Convert.FromBase64String(salt);
        byte[] dst = new byte[src.Length + bytes.Length];
        Buffer.BlockCopy(src, 0, dst, 0, src.Length);
        Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
        HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
        byte[] inArray = algorithm.ComputeHash(dst);
        return Convert.ToBase64String(inArray);
    }
Electrode answered 27/1, 2012 at 13:54 Comment(2)
Fortunately, only the reading of the salt is different from Ryan's implementation. That means that if you have a set of passwords+salts generated by the erroneous implementation and you want to start using the standard membership provider (or at least the right implementation given here), you just have to fix all existing salts in your database : var fixedSalt = Convert.ToBase64String(Encoding.Unicode.GetBytes(oldSalt);Anaximenes
NO SHA1: We are in 2020. Anybody reading this do NOT use SHA1 to hash passwords. Atleast use HashAlgorithm algorithm = HashAlgorithm.Create("SHA512"); if you cannot use PBKDF2 or SCRYPT or ARGON2. PBKDF2 or SCRYPT or ARGON2 are latest generation password hashing algorithms. Unfortunately they are not part of native .NET class libraries as of yet.Connolly
D
28

The default hash algorithm type is SHA1. There are two ways that you can change this.

1) If you are working with IIS 7 you can update this using the "Machine Key" configuration (shown below). This allows you to choose the encryption method from a list of available options and specify the keys or the key generation options.

Machine Key configuration page from IIS 7 administration tool

2) If you are working with IIS 6 you can change the hash algorithm type using the membership element in the web.config file:

<membership
    defaultProvider="provider name"
    userIsOnlineTimeWindow="number of minutes"
    hashAlgorithmType="SHA1">
    <providers>...</providers>
</membership>

According to the documentation the string value of the hashAlgorithmType attribute can be any of the provided .Net hashing algorithm types. A bit of digging shows that the valid values for ASP.Net 2, 3 and 3.5 are MD5, RIPEMD160, SHA1, SHA256, SHA384, SHA512. The important part here is that all these classes inherit from HashAlgorithm.

The value of the hashAlgorithmType attribute can also be an entry from the cryptoNameMapping element in the machine.config file. You could use this if you require a 3rd party hashing algorithm. The machine.config file can typically be found in C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG if you are using ASP.Net 2 or later. You can read more about setting these values here.

Degradation answered 16/7, 2009 at 13:7 Comment(2)
Sadly, Bcrypt isn't anywhere in those lists.Denver
@Joel ASP.Net is extensible though so you could pick up this implementation (derekslager.com/blog/posts/2007/10/…) and expose it either as a custom HashAlgorithm or by creating a custom provider as explained in the other answer.Degradation
L
26

The default hash algorithm changed to HMACSHA256 in the .NET 4.0 Framework.

Note that unlike SHA-1, HMAC SHA-256 is a keyed hash. If your hashes are behaving non-deterministically, you probably haven't set a key, forcing it to use a random one. Something similar to the following would be the culprit (which is what I just spent an hour figuring out :p ).

HashAlgorithm.Create(Membership.HashAlgorithmType)

If you wish to have it work with an existing provider you can revert it back to the former defaults using this guide.

Lezlielg answered 19/11, 2010 at 17:24 Comment(0)
D
3

There is one correction in hashing algorithm, you must use:

byte[] src = Convert.FromBase64String(salt);

instead of

byte[] src = Encoding.Unicode.GetBytes(salt);

Read article http://svakodnevnica.com.ba/index.php?option=com_kunena&func=view&catid=4&id=4&Itemid=5&lang=en#6

Dyspnea answered 4/8, 2011 at 12:37 Comment(0)
C
2

Let's discuss answers to this question which are secure and time-tested:

  1. Zetetic Just two line of code and done! The hashing algorithm PBKDF2 is much better than having SHA1 or SHA256-SHA512 etc. Latest algorithms like PBKDF2, SCRYPT or ARGON2 are leaders when it comes to hashing. But using PBKDF2 is useful in this case as it is implemented by .NET in Rfc2898DeriveBytes class. Using this library was awesome till now but there are some minor issues like:

    a. Zetetic use 5000 iterations by default. Customizable if you use Pbkdf2Hash256K

    b. Zetetic use Rfc2898DeriveBytes and Rfc2898DeriveBytes is based on HMACSHA1 for some reason and can not be customized.

  2. Good news! I have customized Rfc2898DeriveBytes to use HMACSHA512 with 128,000 iterations so that SQLMembershipProvider can use PBKDF2 which was not available so far. To achieve this, I have combined Zetetic's code with my implementation of Rfc2898DeriveBytes as shown below:

using System.Security.Cryptography;

namespace custom.hashing.keyderivation
{
/// <summary>
/// This derived class of PBKDF2Hash provided necessary capabilities to SQLMembershipProvider in order to hash passwords in PBKDF2 way with 128,000 iterations.
/// </summary>
public class PBKDF2Hash : KeyedHashAlgorithm
{
    private const int kHashBytes = 64;

    private System.IO.MemoryStream _ms;

    public int WorkFactor { get; set; }

    public PBKDF2Hash()
        : base()
    {
        this.WorkFactor = 128000;
        this.Key = new byte[32]; // 32 Bytes will give us 256 bits.
        using (var rngCsp = new RNGCryptoServiceProvider())
        {
            // Fill the array with cryptographically secure random bytes.
            rngCsp.GetBytes(this.Key);
        }
    }

    /// <summary>
    /// Hash size in bits
    /// </summary>
    public override int HashSize
    {
        get
        {
            return kHashBytes * 8;
        }
    }

    protected override void HashCore(byte[] array, int ibStart, int cbSize)
    {
        (_ms = _ms ?? new System.IO.MemoryStream()).Write(array, ibStart, cbSize);
    }

    protected override byte[] HashFinal()
    {
        if (this.Key == null || this.Key.Length == 0)
        {
            throw new CryptographicException("Missing KeyedAlgorithm key");
        }

        _ms.Flush();

        var arr = _ms.ToArray();

        _ms = null;

        using (var hmac = new HMACSHA512())
        {
            return new MyRfc2898DeriveBytes(arr, this.Key, this.WorkFactor, hmac).GetBytes(kHashBytes);
        }
    }

    public override void Initialize()
    {
        _ms = null;
    }
}

// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
// <OWNER>Microsoft</OWNER>
// 

//
// Rfc2898DeriveBytes.cs
//

// This implementation follows RFC 2898 recommendations. See http://www.ietf.org/rfc/Rfc2898.txt
/// <summary>
/// Microsoft has implemented PBKDF2 but with HMACSHA1. We are customizing this class to use HMACSHA512 in hashing process.
/// </summary>
public class MyRfc2898DeriveBytes : DeriveBytes
{
    private byte[] m_buffer;
    private byte[] m_salt;
    private HMAC m_hmac;  // The pseudo-random generator function used in PBKDF2

    private uint m_iterations;
    private uint m_block;
    private int m_startIndex;
    private int m_endIndex;

    private int m_blockSize;

    //
    // public constructors
    //

    // This method needs to be safe critical, because in debug builds the C# compiler will include null
    // initialization of the _safeProvHandle field in the method.  Since SafeProvHandle is critical, a
    // transparent reference triggers an error using PasswordDeriveBytes.
    [SecuritySafeCritical]
    public MyRfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, HMAC hmac)
    {
        Salt = salt;
        IterationCount = iterations;
        hmac.Key = password;
        m_hmac = hmac;
        // m_blockSize is in bytes, HashSize is in bits. 
        m_blockSize = hmac.HashSize >> 3;

        Initialize();
    }

    //
    // public properties
    //

    public int IterationCount
    {
        get { return (int)m_iterations; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException("value", "Error: Iteration count is zero or less");

            m_iterations = (uint)value;
            Initialize();
        }
    }

    public byte[] Salt
    {
        get { return (byte[])m_salt.Clone(); }
        set
        {
            if (value == null)
                throw new ArgumentNullException("value");
            if (value.Length < 8)
                throw new ArgumentException("Error: Salt size is less than 8");

            m_salt = (byte[])value.Clone();
            Initialize();
        }
    }

    //
    // public methods
    //

    public override byte[] GetBytes(int cb)
    {
        if (cb <= 0)
        {    throw new ArgumentOutOfRangeException("cb", "Error: Hash size is zero or less"); }

        Contract.Assert(m_blockSize > 0);

        byte[] password = new byte[cb];

        int offset = 0;
        int size = m_endIndex - m_startIndex;
        if (size > 0)
        {
            if (cb >= size)
            {
                Buffer.BlockCopy(m_buffer, m_startIndex, password, 0, size);
                m_startIndex = m_endIndex = 0;
                offset += size;
            }
            else
            {
                Buffer.BlockCopy(m_buffer, m_startIndex, password, 0, cb);
                m_startIndex += cb;
                return password;
            }
        }

        Contract.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer.");

        while (offset < cb)
        {
            byte[] T_block = Func();
            int remainder = cb - offset;
            if (remainder > m_blockSize)
            {
                Buffer.BlockCopy(T_block, 0, password, offset, m_blockSize);
                offset += m_blockSize;
            }
            else
            {
                Buffer.BlockCopy(T_block, 0, password, offset, remainder);
                offset += remainder;
                Buffer.BlockCopy(T_block, remainder, m_buffer, m_startIndex, m_blockSize - remainder);
                m_endIndex += (m_blockSize - remainder);
                return password;
            }
        }
        return password;
    }

    public override void Reset()
    {
        Initialize();
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (disposing)
        {
            if (m_hmac != null)
            {
                ((IDisposable)m_hmac).Dispose();
            }

            if (m_buffer != null)
            {
                Array.Clear(m_buffer, 0, m_buffer.Length);
            }
            if (m_salt != null)
            {
                Array.Clear(m_salt, 0, m_salt.Length);
            }
        }
    }

    private void Initialize()
    {
        if (m_buffer != null)
            Array.Clear(m_buffer, 0, m_buffer.Length);
        m_buffer = new byte[m_blockSize];
        m_block = 1;
        m_startIndex = m_endIndex = 0;
    }

    internal static byte[] GetBytesFromInt(uint i)
    {
        return unchecked(new byte[] { (byte)(i >> 24), (byte)(i >> 16), (byte)(i >> 8), (byte)i });
    }

    // This function is defined as follow :
    // Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i) 
    // where i is the block number.
    private byte[] Func()
    {
        byte[] INT_block = GetBytesFromInt(m_block);

        m_hmac.TransformBlock(m_salt, 0, m_salt.Length, null, 0);
        m_hmac.TransformBlock(INT_block, 0, INT_block.Length, null, 0);
        m_hmac.TransformFinalBlock(new byte[0], 0, 0);
        byte[] temp = m_hmac.Hash;
        m_hmac.Initialize();

        byte[] ret = temp;
        for (int i = 2; i <= m_iterations; i++)
        {
            m_hmac.TransformBlock(temp, 0, temp.Length, null, 0);
            m_hmac.TransformFinalBlock(new byte[0], 0, 0);
            temp = m_hmac.Hash;
            for (int j = 0; j < m_blockSize; j++)
            {
                ret[j] ^= temp[j];
            }
            m_hmac.Initialize();
        }

        // increment the block count.
        if (m_block == uint.MaxValue)
        { throw new InvalidOperationException("Derived key too long."); }

        m_block++;
        return ret;
    }
}

After creating this class do this:

  • Add following line to Application_Start event of Global.asax or respective startup file of your project:

    System.Security.Cryptography.CryptoConfig.AddAlgorithm(typeof(custom.hashing.keyderivation.PBKDF2Hash), "PBKDF2Hash_HB");

  • And change web.config as:

    <membership defaultProvider="sitecore" hashAlgorithmType="PBKDF2Hash_HB">

References for constructing this answer are taken from:

Connolly answered 18/2, 2020 at 3:6 Comment(0)
A
0

I attach a snippet showing the code as in Rawbert's answer above in F#

open System
open System.Security.Cryptography
open System.Text

module PasswordHelper =
    let EncodePassword(pass : string, salt : string) =
        let bytes = Encoding.Unicode.GetBytes(pass)
        let src = Convert.FromBase64String(salt)
        let dst : byte array = Array.zeroCreate (src.Length + bytes.Length)
        Buffer.BlockCopy(src, 0, dst, 0, src.Length)
        Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length)
        let algorithm = HashAlgorithm.Create("SHA1")
        let inArray = algorithm.ComputeHash(dst)
        Convert.ToBase64String(inArray)

This is working code from an active application

Aegeus answered 18/11, 2014 at 9:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.