有效的密码加密

时间:2009-05-19 15:13:43

标签: asp.net security encryption hash

我已经看了一下StackOverflow问题"Password Encryption / Database Layer AES or App Layer AES,",我想在注册(web app)上有效地哈希我的密码,然后能够在登录时检查它们是否正确。我正在使用VB,但使用C#很舒服。

我很想使用".NET Encryption Simplified"中描述的Jeff Atwood的加密类,因为它很容易理解。它有一个散列类 - 但我不知道如何“登录”并在散列后比较散列。这是Jeff使用他的加密类演示他的哈希方法:

Sub DemoHash()
    Dim d As New Encryption.Data( _
        "{ts '2004-10-09 08:10:04'}The world is beautiful and needs caring by its children")

    Dim hash As New Encryption.Hash(Encryption.Hash.Provider.SHA1)
    Dim hash2 As New Encryption.Hash(Encryption.Hash.Provider.SHA256)
    Dim hash3 As New Encryption.Hash(Encryption.Hash.Provider.SHA384)
    Dim hash4 As New Encryption.Hash(Encryption.Hash.Provider.SHA512)
    Dim hash5 As New Encryption.Hash(Encryption.Hash.Provider.MD5)
    Dim hash6 As New Encryption.Hash(Encryption.Hash.Provider.CRC32)

    hash.Calculate(d)
    hash2.Calculate(d)
    hash3.Calculate(d)
    hash4.Calculate(d)
    hash5.Calculate(d)

    Console.WriteLine("SHA1:   " & hash.Value.Hex)
    Console.WriteLine("SHA256: " & hash2.Value.Hex)
    Console.WriteLine("SHA384: " & hash3.Value.Hex)
    Console.WriteLine("SHA512: " & hash4.Value.Hex)
    Console.WriteLine("MD5:    " & hash5.Value.Hex)
    Console.WriteLine("CRC32:  " & hash6.Calculate(d).Hex)
    Console.WriteLine()

    Dim salt As New Encryption.Data("salty!")
    Console.WriteLine("Salted CRC32:  " & hash6.Calculate(d, salt).Hex)

    Console.WriteLine("Press ENTER to continue...")
    Console.ReadLine()
End Sub

所以我的问题是:

  1. 我可以加密密码(虽然我无意存储它)并散列字符串。如果我有一个名为'barry'的用户,密码为'fishlegs',那么存储密码并检索密码的最佳方法是什么?

  2. 在SQL Server中;是二进制还是nvarchar是存储哈希的最佳选择?

  3. 根据'barry'和他的密码,哈希存储有效吗?它是否附加在盐上的“鱼竿”加密?

  4. 密码学很难!

    感谢任何可以提供帮助的人......

8 个答案:

答案 0 :(得分:86)

嗯,我想你只是缺少一些与哈希工作方式有关的基本概念。让我试着简要解释一下。之后我会简单地阐述我的答案,所以请仔细阅读整篇文章,一开始的信息不安全。

您要用于存储密码的功能是一种称为“单向哈希”的功能。这意味着,对于您为函数提供的任何输入,相同的输入将始终给出相同的结果。但是,没有数学过程可以让您获取结果字符串并找出原始输入是什么。

让我们以MD5作为散列函数的一个例子。如果我在字符串“password”上运行MD5,我将始终获得结果“5f4dcc3b5aa765d61d8327deb882cf99”。但是,如果您只是简单地给某人产生结果字符串(“5f4d ...”),那么他们就不可能应用一些数学过程来“反转”该功能并找出它来自“密码”。

这意味着当用户首次设置密码时,会对其应用散列函数,并存储结果。因此,不存储“密码”,而是存储“5f4dcc3b5aa765d61d8327deb882cf99”。然后,当该用户尝试登录时,您将他们键入的内容放入登录表单上的密码框中,并应用相同的散列函数。如果您得到的结果与存储在数据库中的结果相同,则他们必须输入与最初选择的密码相同的密码,即使您实际上不知道原始密码是什么。

现在,即使不可能“反转”散列函数,相同输入始终提供相同输出的事实意味着有人可以简单地建立一个输入/输出对的大数据库,并使用它来有效地反转哈希值。这被称为“彩虹表”。互联网上有许多可用的,因此使用简单的散列是不安全的,以防您的数据库受到损害。也就是说,即使在数学上不可能采取“5f4dcc3b5aa765d61d8327deb882cf99”并且发现它来自在“密码”上运行MD5,在实践中很容易确定。您所要做的就是通过MD5运行字典中的每个单词并存储结果,您可以轻松地反转简单密码。

这就是“salting”的用武之地。如果你为每个用户生成一个随机的“salt”字符串并将其附加到他们的密码,它会有效地破坏彩虹表。例如,假设上面的同一个用户将其密码注册为“密码”。我们生成一个随机的8个字符的盐来附加到他们的密码,然后进行散列。让我们说它是“A4BR82QX”。现在,我们不是哈希“密码”,而是哈希“A4BR82QXpassword”。这给出了结果“87a4ba071c8bcb5efe457e6c4e6c4490”,因此我们将它与salt字符串一起存储在数据库中。然后,当该用户尝试登录时,不是直接散列并比较他们在登录表单中输入的密码,而是取出他们输入的内容,再将“A4BR82QX”放在它前面,然后哈希。和以前一样,如果它与存储的哈希相匹配,我们就知道他们输入了正确的密码。

有效地,你在这里所做的是使预先生成的彩虹表对于试图破解数据库中的密码毫无用处。由于盐是随机的,并且每个用户都有不同的(通常),攻击者必须为每个用户重新生成彩虹表。这要困难得多。

然而,还有一个问题,那就是生成MD5哈希值。尽管像这样的盐化要求它们重新生成彩虹表,但由于MD5的速度有多快,因此可以非常快速地创建一些相当完整的彩虹表。因此,如果他们只是想破解您网站上的高价值帐户,那么花一些时间生成彩虹表以尝试撤消该密码对他们来说并不是什么大不了的事。如果高价值帐户的原始密码本身不够安全,即使使用盐腌,它仍然会很快找到。

所以下一步是找到哈希函数,并使用它来代替像MD5这样的快速哈希函数。让您的网站花费额外的几秒钟来检查登录并不是什么大问题。但是当有人试图生成彩虹表来破解密码时,让每个条目花费几秒钟是一个绝对的杀手。我已经在这里写得足够了,所以我将通过链接到这篇文章来完成,这篇文章详细介绍了如何选择一个好的,缓慢的哈希函数:Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes

这是一个非常大的答案,如果其中任何一个不清楚,请在评论中告诉我,我会编辑精心制作。

答案 1 :(得分:7)

好的,首先关于杰夫班级的一些事情。 SHA1和MD5现已弃用。 CRC32完全不适合密码。其次,您应该对每个哈希值进行腌制,最好使用不同的盐值。通常,您可以为此选择加密随机数据块,但在推送时您可以使用用户名。为您添加前缀,或者在过程中的某个位置加上salt值的后缀。我倾向于哈希密码,哈希盐,结合两者,然后再次哈希。但只要你保持一致,你可以交换它周围的东西并不重要。

因此,不要将事情与杰夫的课程进一步混淆,而是以经典的方式做到这一点。

首先是随机生成盐。

public static byte[] GetRandomSalt()
{
  int minSaltSize = 16;
  int maxSaltSize = 32;

  Random random = new Random();
  int saltSize = random.Next(minSaltSize, maxSaltSize);
  saltBytes = new byte[saltSize];
  RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
  rng.GetNonZeroBytes(saltBytes); 
  return saltBytes;
}

然后哈希

public static byte[] ComputeHash(string plainText)
{
  byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
  HashAlgorithm hash = new SHA256Managed();
  return hash.ComputeHash(plainTextWithSaltBytes);
}

因此,这将计算SHA256哈希并将其作为字节数组返回。

如果你正在腌制,你会做类似以下的事情

byte[] passwordHash = ComputeHash(password);
byte[] salt = GetRandomSalt();
byte[] saltHash = ComputeHash(salt);

byte[] hashWithSaltBytes = new byte[hashBytes.Length + saltBytes.Length];
for (int i=0; i < hashBytes.Length; i++)
  hashWithSaltBytes[i] = hashBytes[i];
for (int i=0; i < saltBytes.Length; i++)
  hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];

然后,如果你感到无聊,请再次将其删除,或保持原样。

要将字节数组转换为字符串,如果您不想存储字节,可以使用

string hashValue = Convert.ToBase64String(hashWithSaltBytes);

字符串比较比字节比较更容易,你必须迭代每个数组,由你决定。请记住,如果您使用随机盐,则需要将它们存储在密码旁边。

答案 2 :(得分:6)

  • 使用用户密码“秘密!”。
  • 生成几个字节的随机盐“ s4L75a1T ”。
  • 将他们称为“ s4L75a1TSecret!”。
  • 计算哈希值“ b9797a5b683804eb195b6ba2a5e368ae74394cd3 ”(这是十六进制的SHA-1)
  • 将哈希值和盐(包括十六进制字符串,Base64字符串或任何您喜欢的内容)与用户名和其他用户信息一起存储(在示例中,salt只是一个纯字符串和哈希值是Base64编码的。)
    FirstName    LastName    Salt        Hash
    -----------------------------------------------------------------
    John         Doe         s4L75a1T    uXl6W2g4BOsZW2uipeNornQ5TNM=

如果要验证用户密码,只需获取用户名,查找salt,再次执行上述操作,查看计算出的哈希值与数据库中的哈希值是否匹配。没有(已知)方法(轻松)恢复密码。

答案 3 :(得分:4)

不要将加密密码与散列密码混淆;如果具有良好的加密散列函数,则无法反转散列过程以检索原始字符串。

上面的乍得答案是对所涉及概念的一个很好的逐点解释。

这个主题在互联网上已经完成;不仅仅是Stack Overflow;认真的 - 一个简单的网络搜索应该找到一个非常全面的指南。

谢谢,杰夫,传播了另一堆错误信息。我想在接下来的一周内我们会看到关于散列,加密等的一系列误导性问题,更不用说浮点运算会出现的废话。

答案 4 :(得分:3)

我相信这个过程是这样的:生成一个随机盐,所以'fishlegs'+'r4nd0mS4lt'得到'fishlegsr4nd0mS4lt'。然后你哈希说,用MD5说(虽然你可能想使用SHA256更安全)得到:593d5518d759b4860188158ef4c71f28。存储它和随机生成的盐。当用户登录时,附加随机盐,然后检查他输入的盐密码是否与数据库中的哈希匹配。

答案 5 :(得分:3)

您基本上想要做的是:

A)在帐户创建时,您会从用户那里获得用户名和密码,将这些用户名和密码与您自己的盐一起散列并将结果字符串存储在数据库中,如:

Dim sUserName = "barry"
Dim sPassWord = "fishlegs"
Dim mySalt = "A deliciously salty string! fillED WIth all KindS of Junkk(&^&*(£"
Dim d As New Encryption.Data(mySalt + sUserName + sPassWord)
Dim hash As New Encryption.Hash(Encryption.Hash.Provider.SHA256)
hash.Calculate(d)
Dim sTheSaltedHashedUnPassCombination = hash.Value.Hex;
SavenewPasswordHashToDatabase(sTheSaltedHashedUnPassCombination)

你永远不会存储sPassWord。

B)当用户登录时,您对提供的用户名和密码执行完全相同的操作,然后将结果哈希值与数据库中先前存储的值进行比较,以便您使用:

Dim sThePreviouslyCreatedHashFromTheDatabase = GetHashForUserFromDatabase(usernameProvided)
Dim mySalt = "A deliciously salty string! fillED WIth all KindS of Junkk(&^&*(£"
Dim d As New Encryption.Data(mySalt + usernameProvided+ passwordProvided)
Dim hash As New Encryption.Hash(Encryption.Hash.Provider.SHA256)
hash.Calculate(d)
Dim sTheSaltedHashedUnPassCombination = hash.Value.Hex;
if (sThePreviouslyCreatedHashFromTheDatabase.Equals(sTheSaltedHashedUnPassCombination))
    'UN & Password Valid!
else
    'UN & PW Invalid!
end

(原谅任何错误,VB不是我的语言)

回答你的问题:

1)见上文。切勿直接存储密码,存储散列值

2)使用char(X),从散列返回的字符数是常量,因此您可以为其指定已知的存储大小。

3)您实际存储的是使用用户名腌制的用户的密码,并且还使用服务器上的常量进行腌制。这将是一个固定长度的字符串,您不能将此字符串更改回单独的用户名和密码。

答案 6 :(得分:2)

  1. 哈希的重点在于你存储的是哈希而不是明文密码而且它们无法恢复(至少只是非常困难)。 Hash functions被设计为单向,因此检索不可能 - 这是他们的本质。
  2. nchar[n]可能是最好的选择,因为常见的哈希算法产生恒定长度的结果(SHA1输出160位哈希)。通常的做法是将散列转换为Base64,以便将其存储为ASCII文本。
  3. salt只是一种将随机位添加到哈希中的方法,以使破解过程复杂化。每增加一点盐意味着蛮力饼干必须花费两倍的时间(并使用两倍的内存)。

答案 7 :(得分:0)

ASP.NET包含一个基于SQL的membership provider,允许您创建,存储和验证encryped or hashed passwords,而无需执行任何操作 - 正如Eric Lippert所说:

  

让我告诉您关于滚动自己的加密算法和安全系统的所有标准警告:不要。