doo*_*urt 12 asp.net security encryption hash
我已经看了StackOverflow问题,"密码加密/数据库层AES或应用层AES",我想在注册(web app)上有效地哈希我的密码然后能够检查它们是登录时更正.我正在使用VB,但使用C#很舒服.
我很乐意使用Jeff Atwood的".NET Encryption Simplified"中描述的加密类,因为它非常容易理解.它有一个散列类 - 但我不知道如何"登录"并在散列后比较散列.这是Jeff使用他的Encryption类演示他的哈希方法:
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
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:
我可以加密密码(虽然我无意存储它)并散列一个字符串.如果我有一个名为'barry'的用户,密码为'fishlegs',那么存储密码并检索密码的最佳方法是什么?
在SQL Server中; 是二进制还是nvarchar是存储哈希的最佳选择?
基于'barry'和他的密码,哈希存储有效吗?它是加在盐上的"鱼竿"的加密吗?
密码学很难!
感谢任何可以提供帮助的人......
Cha*_*rch 85
嗯,我想你只是缺少一些与哈希工作方式有关的基本概念.让我试着简要解释一下.之后我会简单地阐述我的答案,所以请仔细阅读整篇文章,一开始的信息不会安全.
您要用于存储密码的功能称为"单向哈希".这意味着,对于您为函数提供的任何输入,相同的输入将始终给出相同的结果.但是,没有数学过程可以让您获取结果字符串并找出原始输入是什么.
我们以MD5作为散列函数的一个例子.如果我在字符串"password"上运行MD5,我将始终得到结果"5f4dcc3b5aa765d61d8327deb882cf99".但是,如果你只是简单地给某人产生结果字符串("5f4d ......"),他们就不可能应用一些数学过程来"反转"这个功能并弄清楚它来自"密码".
这意味着当用户首次设置密码时,会对其应用散列函数,并存储结果.因此,不存储"密码",而是存储"5f4dcc3b5aa765d61d8327deb882cf99".然后,当该用户尝试登录时,您将他们键入的内容放入登录表单上的密码框中,并应用相同的散列函数.如果您获得的结果与存储在数据库中的结果相同,则必须输入与最初选择的密码相同的密码,即使您实际上不知道原始密码是什么.
现在,即使不可能"反转"散列函数,相同输入始终提供相同输出的事实意味着某人可以简单地建立一个输入/输出对的大数据库,并使用它来有效地反转散列.这被称为"彩虹表".互联网上有许多可用的,因此使用简单的散列是不安全的,以防您的数据库受到损害.也就是说,即使在数学上不可能采用"5f4dcc3b5aa765d61d8327deb882cf99"并且发现它来自在"密码"上运行MD5,但在实践中很容易确定.您所要做的就是通过MD5运行字典中的每个单词并存储结果,您可以轻松地反转简单密码.
这就是"salting"的用武之地.如果你为每个用户生成一个随机的"salt"字符串并将其附加到他们的密码,它会有效地破坏彩虹表.例如,假设上面的同一个用户将其密码注册为"密码".我们生成一个随机的8个字符的盐来附加到他们的密码,然后进行散列.让我们说它是"A4BR82QX".现在,我们不是哈希"密码",而是哈希"A4BR82QXpassword".这给出了结果"87a4ba071c8bcb5efe457e6c4e6c4490",因此我们将它与salt字符串一起存储在数据库中.然后,当该用户尝试登录时,不是直接散列并比较他们在登录表单中输入的密码,而是取出他们输入的内容,再将"A4BR82QX"放在它前面,然后哈希.和以前一样,如果它与存储的哈希匹配,我们知道他们输入了正确的密码.
实际上,您在此处所做的就是使预先生成的彩虹表对于尝试破解数据库中的密码毫无用处.由于盐是随机的,并且每个用户具有不同的(通常),攻击者将不得不为每个用户重新生成他们的彩虹表.这要困难得多.
但是,还有一个问题,那就是生成MD5哈希值很快.尽管像这样的盐化要求它们重新生成彩虹表,但由于MD5的速度有多快,因此可以非常快速地创建一些相当完整的彩虹表.因此,如果他们只是想破解您网站上的高价值帐户,那么花一些时间生成彩虹表以尝试撤消该密码对他们来说并不是什么大不了的事.如果高价值帐户的原始密码本身不够安全,即使使用腌制,它仍然会很快找到.
所以下一步是找到一个慢哈希函数,并使用它而不是像MD5这样的快速哈希函数.让您的网站花费额外的几秒钟来检查登录并不是什么大问题.但是当有人试图生成彩虹表来破解密码时,让每个条目花费几秒钟是一个绝对的杀手.我已经在这里写得足够了,所以我将完成链接到这篇文章,其中详细介绍了选择一个好的,缓慢的哈希函数:足够的彩虹表:你需要知道的安全密码方案.
这是一个非常大的答案,如果其中任何一个不清楚,请在评论中告诉我,我将编辑详细说明.
好的,首先关于Jeff的课程有几件事.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;
}
Run Code Online (Sandbox Code Playgroud)
然后哈希
public static byte[] ComputeHash(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
HashAlgorithm hash = new SHA256Managed();
return hash.ComputeHash(plainTextWithSaltBytes);
}
Run Code Online (Sandbox Code Playgroud)
因此,这将计算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];
Run Code Online (Sandbox Code Playgroud)
然后,如果你感到无聊,请再次将其删除,或保持原样.
要将字节数组转换为字符串,如果您不想存储可以使用的字节
string hashValue = Convert.ToBase64String(hashWithSaltBytes);
Run Code Online (Sandbox Code Playgroud)
字符串比较比字节比较更容易,您必须迭代每个数组,由您决定.请记住,如果您使用随机盐,则需要将它们存储在密码旁边.
FirstName LastName Salt Hash
-----------------------------------------------------------------
John Doe s4L75a1T uXl6W2g4BOsZW2uipeNornQ5TNM=
如果要验证用户密码,只需获取用户名,查找salt,再次执行上述操作,查看计算出的哈希值与数据库中的哈希值是否匹配.没有(已知)方法(轻松)恢复密码.