ASP.NET 成员使用的默认哈希算法是啥?

Posted

技术标签:

【中文标题】ASP.NET 成员使用的默认哈希算法是啥?【英文标题】:What is default hash algorithm that ASP.NET membership uses?ASP.NET 成员使用的默认哈希算法是什么? 【发布时间】:2010-11-11 08:43:05 【问题描述】:

ASP.NET 成员使用的默认哈希算法是什么?我该如何改变它?

【问题讨论】:

【参考方案1】:

让我们讨论一下这个问题的答案,这些答案是安全且经过时间考验的:

    Zetetic 两行代码就搞定了!哈希算法 PBKDF2 比 SHA1 或 SHA256-SHA512 等要好得多。最新的算法,如 PBKDF2、SCRYPT 或 ARGON2 是领先者散列。但是在这种情况下使用 PBKDF2 很有用,因为它是由 .NET 在Rfc2898DeriveBytes 类中实现的。到目前为止,使用这个库非常棒,但是有一些小问题,例如:

    一个。 Zetetic 默认使用 5000 次迭代。如果您使用Pbkdf2Hash256K,则可自定义

    b. Zetetic 使用Rfc2898DeriveBytesRfc2898DeriveBytes 基于某种原因是基于HMACSHA1,不能自定义。

    好消息!我已经自定义 Rfc2898DeriveBytes 以使用 HMACSHA512 进行 128,000 次迭代,以便 SQLMembershipProvider 可以使用迄今为止不可用的 PBKDF2。为此,我将Zetetic's 代码与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 更改为:

&lt;membership defaultProvider="sitecore" hashAlgorithmType="PBKDF2Hash_HB"&gt;

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

Zetetic 的 github code EVK 的answer 微软参考code

【讨论】:

【参考方案2】:

above answer by Ryan Christensen 不完整。将 salt 转换为 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);
    

【讨论】:

幸好只是salt的读取与Ryan的实现不同。这意味着如果您有一组由错误实现生成的密码+盐,并且您想开始使用标准成员资格提供程序(或至少此处给出的正确实现),您只需修复数据库中所有现有的盐: var fixedSalt = Convert.ToBase64String(Encoding.Unicode.GetBytes(oldSalt); NO SHA1:我们在 2020 年。任何阅读本文的人都不要使用 SHA1 来散列密码。如果您不能使用 PBKDF2 或 SCRYPT 或 ARGON2,至少使用 HashAlgorithm algorithm = HashAlgorithm.Create("SHA512");。 PBKDF2 或 SCRYPT 或 ARGON2 是最新一代的密码散列算法。不幸的是,它们目前还不是本机 .NET 类库的一部分。【参考方案3】:

编辑:不要按原样使用 Membership Provider,因为它在保护用户密码方面非常不足

鉴于googling "membership provider hashing algorithm" 将这个答案作为第一个结果,以及将被推断的福音,我有必要警告人们不要像这样使用 Membership Provider 并使用像 SHA-1 这样的哈希值, MD5 等来混淆数据库中的密码。

tl;博士

Use a key-derivation function like bcrypt, scrypt or (if you need FIPS compliance) PBKDF2 的工作系数足以使单个密码的哈希时间接近 1000 毫秒或更长。

如今,哈希很容易通过最近历史上大量的数据泄露示例进行暴力破解。为防止您的用户密码在下一次黑客攻击中出现在 pastebin 中,请确保使用需要足够长时间计算的函数对密码进行哈希处理!

至少尝试IdentityReboot 或newer implementations from Microsoft that Troy Hunt talks about,而不是会员资格提供者。

有趣的是,在上面提到的同一个谷歌搜索结果中,我发现tutorial showing folks preciously how easy it is 可以使用 JtR 或 Hashcat 等流行工具来暴力破解这些密码哈希。在自定义 GPU 装备上,SHA1 可以在 staggering rate of 48867 million hashes per second! 上破解,使用像 rockyou or the like 这样的免费字典,一个有动力的人会很快拥有你的大部分用户密码。作为开发人员,您有道德责任采取必要措施保护用户密码的安全。


默认散列是 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

【讨论】:

这里的EncodePassword函数不起作用。请参阅下面“Rawbert”的工作示例。 在流行的蛮力技术和 SHA-1 哈希(及其突变)的速度的背景下,我对“SHA-1 现在相当不错”这一行提出了严重的问题破解,这是公认的答案,所以很多人都会信任它。 可能更容易说这是 2009 年问题得到解答时的解决方案,而不是我希望现在大多数开发人员都知道这一点的冗长响应。我相信大多数优秀的开发人员都会检查解决方案的日期,并且可能仅在处理一些今天仍然运行不充分散列的遗留系统时才使用它。 2009 年的 SHA-1 “现在还不错”,“现在”已经过去了。 @RyanChristensen 是完全正确的。现在 ASP.Net Identity 使用 pbkdf2 和 10k 次迭代(SHA256/128-bit salt/256-bit subey),这是非常流行的。 @faester 抱歉,我应该说它是 aspnet Identity 的第 3 版,所以可能不太有用...github.com/aspnet/Identity/blob/dev/src/…【参考方案4】:

我附上了一个显示代码的 sn-p,如上面 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)

这是来自活动应用程序的工作代码

【讨论】:

【参考方案5】:

哈希算法有一处更正,你必须使用:

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

而不是

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

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

【讨论】:

【参考方案6】:

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

请注意,与 SHA-1 不同,HMAC SHA-256 是键控哈希。如果您的哈希表现不确定,您可能没有设置密钥,强制它使用随机密钥。罪魁祸首可能类似于以下内容(这是我花了一个小时才弄清楚的 :p )。

HashAlgorithm.Create(Membership.HashAlgorithmType)

如果您希望它与现有提供商一起使用,您可以使用 this guide 将其恢复为以前的默认值。

【讨论】:

【参考方案7】:

默认哈希算法类型为 SHA1。有两种方法可以改变这一点。

1) 如果您使用的是 IIS 7,您可以使用“机器密钥”配置(如下所示)进行更新。这允许您从可用选项列表中选择加密方法并指定密钥或密钥生成选项。

2) 如果您使用的是 IIS 6,您可以使用 web.config 文件中的成员资格元素更改哈希算法类型:

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

根据文档,hashAlgorithmType 属性的字符串值可以是任何提供的 .Net 散列算法类型。一点挖掘表明,ASP.Net 2、3 和 3.5 的有效值为 MD5RIPEMD160SHA1SHA256SHA384SHA512。这里重要的部分是所有这些类都继承自HashAlgorithm

hashAlgorithmType 属性的值也可以是 machine.config 文件中 cryptoNameMapping 元素中的条目。如果您需要第 3 方散列算法,则可以使用它。如果您使用的是 ASP.Net 2 或更高版本,则 machine.config 文件通常可以在 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG 中找到。您可以阅读有关设置这些值的更多信息here。

【讨论】:

遗憾的是,Bcrypt 不在这些列表中。 @Joel ASP.Net 是可扩展的,因此您可以选择此实现 (derekslager.com/blog/posts/2007/10/…) 并将其公开为自定义 HashAlgorithm 或通过创建自定义提供程序,如另一个答案中所述。

以上是关于ASP.NET 成员使用的默认哈希算法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

谁能告诉我哈希是啥?

ASP.NET 回发会丢失 URL 中的哈希值

Asp.net Identity 密码哈希

ASP.NET Core Identity - 扩展密码哈希

C++ std::unordered_map 中使用的默认哈希函数是啥?

哈希算法学习