【问题标题】:Correctly Salting a Hash in .NET在 .NET 中正确加盐哈希
【发布时间】:2014-05-19 00:46:05
【问题描述】:

我正在我公司当前的产品中实施一个新的密码存储过程。它是一个带有 MS SQL Server 的 ASP.NET 应用程序。

在他们使用同一个公共种子的 3DES 加密进行加密和检查用户身份验证之前,他们只是使用同一个种子解密了密码。

我正在实现一个 SHA256 哈希,其盐无法解密。

首先,我知道每个用户的每种盐都应该不同,但我不明白这种盐将存储在哪里?如果它存储在数据库中,这不就失去了它的用途吗?

我的想法是通过获取用户名的前 4 个字母、名字的前 3 个字母和姓氏的前 3 个字母来创建盐,并将其转换为 md5 哈希,然后将其用作盐没有存储在数据库中。

此序列将是服务器端的,因此没有源代码,任何黑客都无法知道该序列。

我在这里做的有什么问题吗?

SHA256 也可以接受,或者我应该查看 SHA512。

谢谢

【问题讨论】:

  • 为什么说“每个用户的每种盐都应该不同”? 如果是这种情况,那么是的,您必须存储它,否则您将永远无法进行有效的比较。我将我的 SALT 定义为代码中的常量表达式;我部署的任何站点都没有被黑客入侵,尽管已经进行了几次尝试。如果这对他们来说足够好,它对你有用吗?
  • @sh1rts:您应该为每个用户使用随机盐。整个网站的单一盐会降低安全性(然后,一个彩虹表适用于您网站上的每个用户帐户)。
  • @sh1rts:对所有密码都相同的盐不是盐,它只是一些填充。

标签: asp.net sql-server vb.net security encryption


【解决方案1】:

如果你绝对必须自己实现这个(我个人是MembershipReboot 的粉丝,那么你应该看看PBKDF2 的密码存储。

它不仅可以正确实施盐,还支持多次迭代以帮助阻止暴力攻击。您可以找到有关迭代次数的指导 here

另外值得注意的是,如果验证是一个问题,PBKDF2 是可以接受的NIST standard

要回答有关将盐保存在数据库中的问题,无需保密。盐的目的只是为了防止预先计算散列,而不是以任何方式混淆或“加密”。

Rainbow tables 通过预先计算密码工作,然后在暴力破解时,在彩虹表中查找相应的哈希值。您实际上可以通过谷歌搜索 MD5 哈希来了解它是多么简单,并且通常在搜索结果中您会找到原始输入。

例如,如果您搜索字符串“5f4dcc3b5aa765d61d8327deb882cf99”,您会发现它对应于“密码”。

通过使用盐,攻击者必须为每个可能的密码计算唯一的哈希值,而不仅仅是一个通用列表。

【讨论】:

  • 如果盐是已知的,(如果存储在数据库中)在蛮力攻击中,每个测试案例都可以只附加或预先添加已知的盐,从而避免一起使用盐.这就是为什么我认为盐不应该存储在数据库中?
  • @user2338488,正如 mfanto 所说,盐用于防止预先计算的哈希(例如彩虹表)。加盐不应该阻止实时计算哈希,所以盐不需要保密。
【解决方案2】:

“我在这里做的事情有什么问题吗?”

是的,有。默默无闻不是安全。仅仅因为盐很难找到并不意味着它非常安全。与强制散列相比,弄清楚你是如何创建盐的将是小菜一碟。

不需要对盐保密,只需为每个用户创建一个随机数并与密码一起存储。 salt的目的是消除使用彩虹表破解表中所有密码的优势。对于大多数用户来说,盐必须是不同的(最好是唯一的,但这并不重要)。

【讨论】:

  • 你能解释一下计算盐有多容易吗?如果是在服务器端生成然后散列?就像我在之前的问题评论中一样,如果盐是已知的,虽然很昂贵,但程序员不能调整他们的蛮力攻击以将已知的哈希添加到每个蛮力字符串中。只要安全不是主要的安全机制,我总是通过默默无闻的方式学习安全。
  • @user2338488:这很容易,因为它知道它在哪里。如果盐不是单独存储的,那么它是根据用户的值计算出来的,这只是在正确的组合中尝试正确的值的问题。盐不是用来使蛮力攻击复杂化,而是用来使攻击者不得不使用蛮力而不是更有效的方法。在安全性中使用晦涩难懂比根本没有安全性更糟糕,因为它给人一种虚假的安全感。主要的安全方法应该足够好,以至于根本不需要隐晦。
  • 你知道它在哪里是什么意思?在正确的组合中尝试正确的值,看看是什么产生了盐?但是盐在哈希中,而不是一目了然。除非您已经成功地利用了哈希,否则您不会知道盐吗?我了解加盐仅用于防止预计算。但是如果你知道盐,你仍然可以预先计算。例如,我知道 salt 是 _123,因此要测试用户密码是否为“密码”,我只需将已知的 salt 预先或附加到每次暴力尝试中,从而避免对 salt 的需要......
  • @user2338488:预计算只有在您有多个使用相同盐的密码时才有任何好处。否则,它与蛮力是一样的。
  • 好的,我明白了。如果盐是未知的,我仍然觉得好像有一些额外的安全性(通过暴力强制的昂贵时间)。如果它知道已散列的字符串的盐端,那么蛮力将更容易尝试。在不知道盐的情况下,蛮力必须分解密码长度 + PBKDF2 中散列的 32 个字符。这将是一个漫长而缓慢的尝试。
【解决方案3】:

盐不应该是私有的。它与散列一起分发,通常附加在它前面。 salt的唯一目的是确保如果相同的数据被加密两次,你永远不会得到相同的输出。根据定义,它必须是唯一的,但不需要随机性或保密性。

您也不应该为每个用户创建一个随机数。 每个加密的salt都需要不同,即使是同一个用户。只需生成一个随机salt,用它来创建你的散列,将salt添加到散列中,将所有内容编码为base64,然后存储它。要验证您解码为二进制的哈希值,请提取 salt,使用它对输入数据进行哈希处理并与解码后的哈希值进行比较。

作为盐效果很好的东西是 GUID。 128 位,根据定义是唯一的,虽然不是完全随机的,并且无需任何额外代码即可立即使用。这是 AES-256 加密和解密的示例。请记住,在实际实现中,您希望将纯文本和密钥存储在 SecureString 对象中...

Private blockSize = 128
Private keySize = 256
Private cipherMode = CipherMode.CBC

Protected Function AESEncryptStringToBase64(strPlainText As String, strKey As String) As String
    Dim Algo As AesManaged = AesManaged.Create()
    With Algo
        .BlockSize = blockSize
        .FeedbackSize = blockSize
        .KeySize = keySize
        .Mode = cipherMode
        .IV = Guid.NewGuid().ToByteArray()
        .Key = Encoding.ASCII.GetBytes(strKey)
    End With

    Using Encryptor As ICryptoTransform = Algo.CreateEncryptor()
        Using MemStream As New MemoryStream
            Using CryptStream As New CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write)
                Using Writer As New StreamWriter(CryptStream)
                    Writer.Write(strPlainText)
                End Using

                AESEncryptStringToBase64 = Convert.ToBase64String(Algo.IV.Concat(MemStream.ToArray()).ToArray())
            End Using
        End Using
    End Using
End Function

Protected Function AESDecryptBase64ToString(strCipherText As String, strKey As String) As String
    Dim arrSaltAndCipherText As Byte() = Convert.FromBase64String(strCipherText)

    Dim Algo As AesManaged = AesManaged.Create()
    With Algo
        .BlockSize = blockSize
        .FeedbackSize = blockSize
        .KeySize = keySize
        .Mode = cipherMode
        .IV = arrSaltAndCipherText.Take(16).ToArray()
        .Key = Encoding.ASCII.GetBytes(strKey)
    End With

    Using Decryptor As ICryptoTransform = Algo.CreateDecryptor()
        Using MemStream As New MemoryStream(arrSaltAndCipherText.Skip(16).ToArray())
            Using CryptStream As New CryptoStream(MemStream, Decryptor, CryptoStreamMode.Read)
                Using Reader As New StreamReader(CryptStream)
                    AESDecryptBase64ToString = Reader.ReadToEnd()
                End Using
            End Using
        End Using
    End Using
End Function

【讨论】:

    猜你喜欢
    • 2011-05-27
    • 1970-01-01
    • 2012-11-04
    • 2011-12-06
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多