ASP.NET会员资格使用的默认哈希算法是什么?

63

ASP.NET Membership使用的默认哈希算法是什么?我如何更改它?

7个回答

51

编辑:不要使用成员资格提供程序,因为它在保护用户密码方面非常不足

鉴于谷歌搜索“membership provider哈希算法”将此答案列为第一个结果,并且将被推断的基督教福音,我有责任警告人们不要像这样使用Membership Provider和使用SHA-1、MD5等哈希值来对数据库中的密码进行混淆。

tl;dr(太长不看)

使用派生密钥函数,如bcrypt、scrypt或(如果需要符合FIPS标准)PBKDF2,其中工作因子足以使单个密码的哈希时间尽可能接近1000ms或更长时间。

现在,哈希很容易被强力破解,最近历史上还有大量数据泄漏的例子。为了防止用户的密码在下一次黑客攻击中出现在pastebin上,请确保密码使用需要充分长时间计算的函数进行哈希!

请尝试使用IdentityReboot或至少Troy Hunt讨论的Microsoft的新实现,而不是Membership Provider。

有趣的是,在上述谷歌搜索结果中,我发现一篇教程向人们展示了使用流行工具如JtR或Hashcat轻松破解这些密码哈希的方法。在自定义GPU设备上,SHA1可以以惊人的速度每秒48867万个哈希值被破解!使用免费字典如rockyou或类似字典,一个有动力的人很快就能获得大多数用户的密码。作为开发者,保护用户密码安全是你的道德责任。


默认的哈希算法是SHA1,但他们也会对其进行盐值处理和base64编码:

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);
}

如果您想了解如何更改它,我仍需要找出(除非使用自定义提供程序,请参见下文),但是现在SHA-1还不错。 如果您要反转它或从中查找,这些人已经做了一些工作:http://forums.asp.net/p/1336657/2899172.aspx 此SO问题将有助于反转或复制此技术,如果需要的话。 Reimplement ASP.NET Membership and User Password Hashing in Ruby 如果您正在创建自定义提供程序,则可以创建哈希和加密算法以及方法。
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


6
这里的EncodePassword函数无法正常工作。请看下面"Rawbert"提供的可用示例。 - connorbode
1
我对“SHA-1现在还不错”的说法非常不满,尤其是在流行的暴力破解技术和SHA-1哈希(以及变种)被破解的速度方面。而且这个答案已经被接受,很多人会把信任赋予它。 - Matt Kocaj
3
可能更容易的做法是在回答问题时注明这是2009年的解决方案,而不是过于冗长的回复,我希望大多数开发人员现在都知道这一点。我相信大多数优秀的开发人员会检查解决方案的日期,并且只有在处理一些仍在使用不足哈希的遗留系统时才会使用它。在2009年,SHA-1算法“现在还好”,但这种情况已经过去了。 - Ryan Christensen
嗨@davewasthere - 如果我查看https://referencesource.microsoft.com/mscorlib/a.html#2f0361c615528d06,它仍然看起来像是使用HMACSHA1而不是SHA256。我错过了什么? - faester
1
@faester 抱歉,我应该说它是 aspnet Identity 的第3版,所以可能不太有用... https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity/PasswordHasher.cs - davewasthere
显示剩余2条评论

40

Ryan Christensen的上面回答并不完整。它将盐转换为byte[]的部分不正确。

这是我在为客户实现解决方案时使用的可行示例:

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);
    }

2
幸运的是,与Ryan的实现不同的只是盐的读取。这意味着,如果您有一组由错误实现生成的密码+盐,并且您想开始使用标准成员资格提供程序(或至少在此处给出正确的实现),则只需修复数据库中的所有现有盐:var fixedSalt = Convert.ToBase64String(Encoding.Unicode.GetBytes(oldSalt); - jpatte
没有SHA1:我们已经进入了2020年。任何人阅读此内容都不要使用SHA1来哈希密码。 如果您不能使用PBKDF2或SCRYPT或ARGON2,请至少使用HashAlgorithm algorithm = HashAlgorithm.Create("SHA512");。 PBKDF2或SCRYPT或ARGON2是最新一代的密码哈希算法。不幸的是,它们目前还不是.NET类库的一部分。 - jitin14

28
默认的哈希算法类型是SHA1。有两种方法可以更改此设置。
1)如果您正在使用IIS 7,可以使用“Machine Key”配置来更新此设置(如下所示)。这使您可以从可用选项列表中选择加密方法,并指定密钥或密钥生成选项。

Machine Key configuration page from IIS 7 administration tool

2) 如果您正在使用IIS 6,可以通过web.config文件中的membership元素更改哈希算法类型:

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

根据文档,hashAlgorithmType 属性的字符串值可以是任何提供的 .Net 哈希算法类型。一些挖掘显示,ASP.Net 2、3 和 3.5 的有效值为 MD5RIPEMD160SHA1SHA256SHA384SHA512。重要的部分在于所有这些类都继承自 HashAlgorithmhashAlgorithmType 属性的值也可以是 machine.config 文件中 cryptoNameMapping 元素的条目。如果需要第三方哈希算法,则可以使用此选项。如果您正在使用 ASP.Net 2 或更高版本,则可以在 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG 中找到 machine.config 文件。您可以在 here 中阅读更多关于设置这些值的信息。

5
遗憾的是,这些列表中都没有Bcrypt。 - Joel Coehoorn
2
@Joel ASP.Net是可扩展的,因此您可以使用此实现(http://derekslager.com/blog/posts/2007/10/bcrypt-dotnet-strong-password-hashing-for-dotnet-and-mono.ashx)并将其公开为自定义HashAlgorithm或通过创建自定义提供程序来解释其他答案中所述。 - MikeD

26

在 .NET 4.0 Framework 中,默认的哈希算法已更改为HMACSHA256。

请注意,与 SHA-1 不同,HMAC SHA-256 是一种密钥哈希。如果您的哈希表现出非确定性行为,则可能没有设置密钥,强制使用随机密钥。类似以下内容可能会是罪魁祸首(这就是我刚刚花了一个小时找出来的 :p)。

HashAlgorithm.Create(Membership.HashAlgorithmType)

如果您希望它能够与现有的提供程序一起使用,您可以使用此指南将其恢复为以前的默认设置。


3

2
让我们讨论一下安全且经得起时间考验的答案:
  1. Zetetic 只需两行代码就可以完成! 哈希算法PBKDF2比使用SHA1或SHA256-SHA512等更好。像PBKDF2、SCRYPT或ARGON2这样的最新算法在哈希方面处于领先地位。但在这种情况下使用PBKDF2很有用,因为.NET中的Rfc2898DeriveBytes类实现了它。使用这个库到目前为止非常棒,但是还有一些小问题,例如:

    a. Zetetic默认使用5000次迭代。如果使用Pbkdf2Hash256K,则可自定义。

    b. Zetetic使用Rfc2898DeriveBytes,而Rfc2898DeriveBytes基于某种原因的HMACSHA1,无法自定义。

  2. 好消息!我已经定制了Rfc2898DeriveBytes,以使用128,000次迭代的HMACSHA512,以便SQLMembershipProvider可以使用迄今为止不可用的PBKDF2。为了实现这一点,我将Zetetic的代码与我的Rfc2898DeriveBytes实现组合起来,如下所示:

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;
    }
}

创建这个类后,请执行以下操作:
  • 将以下行添加到Global.asax的Application_Start事件或项目的相应启动文件中:

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

  • 并将web.config更改为:

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

构建此答案的参考资料来自于:


0

我附上一个代码片段,展示了与Rawbert在上面给出的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)

这是来自一个活跃应用的可运行代码


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接