首选的密码存储方法是什么?在数据库中。

77

你在数据库(最好是SQL Server 2005)中存储密码时,更喜欢使用什么方法/数据类型?我在我们几个应用程序中一直使用的方法是首先使用.NET加密库,然后将其作为二进制(16)存储在数据库中。这是否是首选方法或者我应该使用不同的数据类型或分配更多的空间超过16个字节?


1
我有一种感觉这可能是一个重复的问题,但我认为它可能是一个关于实际数据库登录而不仅仅是存储应用程序/站点登录的问题。 - TheTXI
1
请问您能否明确您的问题,以便说明您使用的是“加密”还是“哈希”函数?我认为您指的是后者,但区别非常重要。 - ine
10个回答

82

我会在数据库中存储密码的盐值哈希等效物,而不是原始密码。然后将这个哈希值与用户输入的密码生成的哈希值进行比较。

在任何地方都不应该存储明文密码数据,这样做太危险了。这使得找回密码变得不可能,但当有人忘记或丢失密码时,可以通过一些检查来创建一个新密码。


@Quinton:这基本上就是我的做法。使用 .net 加密库,它会将密码哈希化,然后我将其传递到数据库并存储为二进制。我只能通过哈希用户输入并将其与存储的内容进行比较来比较密码。 - TheTXI
1
如果你在使用Java,我推荐使用jbcrypt。非常简单易用。 - Jens Schauder
14
我们也会存储加盐密码。在我们的情况下,我们将 160 位哈希转换为 base64,并将其存储在 varchar 中 - 这只是为了使事情更容易(字符串更易于阅读/编写/查看)。除了全局盐之外,我们还使用用户的 id(sid 或 guid)进行预盐处理,这样使用相同密码的两个用户不会生成相同的哈希值。这使得字典攻击更加困难(CPU 成本更高),并且在多个用户选择相同密码时,防止同时发现多个密码。 - Ian Boyd
@IanBoyd 如果你还没有改变salt的方法,那么你做错了。它应该是随机的,具有足够的熵并且与用户相关,而在这种情况下,你只满足后者。 - Francisco Presencia
@FranciscoPresencia 从那以后,我已经转换到了bcrypt。规范的bcrypt实现为每个哈希生成随机盐。规范的实现还将22字节的哈希转换为base64,适合在“varchar”数据库列中轻松存储。他们考虑得最后一点是定义其哈希字符串的格式,其中盐包含在字符串中。 - Ian Boyd

48

首选方法:不要将密码存储在您的数据库中,只存储其哈希值。加入盐,根据口味而定。


6
升级了,不得不这么做,只是为了调侃一下 ;) - Jakub

15

我做的和你描述的一样,只不过我的存储是以字符串的形式。 我会将加密后的二进制值进行Base64编码。需要分配的空间量取决于加密算法/密码强度。

我认为你做得很对(假设你使用了Salt)。


9
  1. 将加盐密码的哈希值存储,例如bcrypt(nounce+pwd)。您可能更喜欢bcrypt而不是SHA1或MD5,因为它可以调整为CPU密集型,从而使暴力攻击变得更加困难。
  2. 在几次登录错误后,在登录表单中添加验证码(以避免暴力攻击)
  3. 如果您的应用程序有“忘记密码”链接,请确保它不会通过电子邮件发送新密码,而应该发送到一个(安全的)页面的链接,允许用户定义一个新密码(可能只有在确认一些个人信息后,如用户的出生日期,例如)。此外,如果您的应用程序允许用户定义新密码,请确保要求用户确认当前密码。
  4. 显然,要保护登录表单(通常使用HTTPS)和服务器本身

通过这些措施,您的用户密码将相对较好地受到以下攻击的保护:

  1. => 离线字典攻击
  2. => 在线字典攻击
  3. => 拒绝服务攻击
  4. => 各种攻击!

所有这些措施都可以快速实施,付出最小的努力就能产生巨大效果!+1 =) - Luke Antins
DOS攻击与密码安全有关吗? - Arj
1
@a12jun:如果您的网站上有一个简单的“登录”输入框和一个“请生成新密码并通过电子邮件发送给我”的按钮,那么任何知道您登录名的人都可以强制您更改密码。如果他自动化每10秒运行一次此操作,则他可以拒绝您访问您的帐户。如果他对数千个用户运行此操作,则可能会让很多人不喜欢您的服务。指南#3可以保护您免受此类攻击,这就是我的意思(我必须承认,这不是很清楚)。安全=机密性,完整性,可用性。 - MiniQuark

8
由于哈希函数的结果是在范围0到255(或-128到127,取决于您的8位数据类型的符号),因此将其存储为原始二进制字段是最有意义的,因为它是最紧凑的表示形式并且不需要额外的编码和解码步骤。
一些数据库或驱动程序对二进制数据类型的支持不够好,或者有时开发人员对它们不太熟悉以至于不舒服。在这种情况下,使用二进制到文本编码(如Base-64或Base-85),并将生成的文本存储在字符字段中是可接受的。
所需字段的大小由您使用的哈希函数确定。 MD5始终输出16个字节,SHA-1始终输出20个字节。一旦选择了哈希函数,通常就无法更改,因为更改需要重置所有现有密码。因此,使用可变大小字段并没有什么优势。

关于执行哈希的“最佳”方法,我已经尝试为其他 SO 问题提供了许多答案:


如果您仔细阅读我的问题,您会发现我更关注字段的数据类型和分配的空间方面的实际存储。 - TheTXI
但是加1是为了为那些后来可能会遇到问题的人提供额外的信息。 - TheTXI
好的。看起来我不是唯一一个错过了那个东西 ;) - erickson

4

我使用用户名的sha哈希值、web config中的guid和以varchar(40)格式存储的密码。如果他们想要暴力破解/字典攻击,他们需要入侵web服务器获取guid。如果他们找到了密码,用户名会在整个数据库中创建彩虹表时被破解。如果用户想要更改用户名,我会同时重置密码。

System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(
    username.ToLower().Trim(),
    ConfigurationManager.AppSettings("salt"),
    password
);

我曾经使用用户名作为添加盐的方式。但之后我们无法重命名用户。现在,我们改用 salt,并将其设置为用户的 ID、GUID、SID 或其他代理键。 - Ian Boyd
好的,没错。这样你就不必在重命名用户时重置密码了。 - Shawn

3

超越表面安全问题的重要链接。对于故意缓慢的算法,是否有必要在客户端计算哈希值,以免过载(可能已经非常繁忙)的服务器? - cheduardo
并不是真的要让它变慢,目标只是稍微慢一点。这只是为了给你一个想法。在四核系统上,我可以每秒哈希约200K个密码。我怀疑没有人需要处理每秒200K个同时登录……因此,您可以使用像PBKDF2或bcrypt这样的函数来将其减慢到每秒仅100个哈希(将暴力破解时间表增加了200倍)。 - Gerald Davis

3
您可以在数据库中使用多个哈希值,只需要额外付出一些努力。如果您认为未来有可能需要支持其他格式,那么这非常值得。我经常使用像以下这样的密码条目:
{hashId}${salt}${hashed password}
其中,“hashId”只是我内部用于识别某个特定哈希模式(例如SHA1)的数字;“salt”是一个base64编码的随机盐;而“hashed password”是一个base64编码的哈希值。如果您需要迁移哈希值,您可以拦截使用旧密码格式的人,并让他们在下次登录时更改密码。
正如其他人所提到的,您需要小心处理哈希值,因为很容易做出不太安全的事情。例如,H(salt,password)比H(password,salt)要弱得多,但同时您需要平衡投入的工作量和站点内容的价值。我经常使用H(H(password,salt),password)。
最后,与能够使用各种期望文本数据的工具相比,使用base64编码密码的成本是适度的。是的,它们应该更加灵活,但您准备告诉老板他不能使用他喜欢的第三方工具,因为您想节省每个记录的几个字节吗? :-)
编辑添加另一个评论:如果我建议故意使用算法来燃烧每个密码的1/10秒,那么我很幸运只是被赶出老板的办公室。 (不太幸运?他会记下一些东西,在我的下一个年度审查时讨论。)当您有数十个甚至数百个用户时,燃烧时间并不成问题。如果您推动了10万个用户,通常会有多个人同时登录。您需要快速而强大的东西,而不是慢而强大的东西。 “但信用卡信息呢?”最好是虚伪的,因为存储的信用卡信息不应该出现在您的常规数据库中,并且将由应用程序加密,而不是由单个用户加密。

在哈希方面,快速和强大并不存在。如果对你来说很快,那么对于暴力攻击者来说也是很快的。使用密钥派生函数是一种有效的密码加固方法。 - Gerald Davis

2
由于您的问题涉及存储方法和大小,我将对此进行说明。
存储类型可以是二进制或文本表示(base64是最常见的)。 二进制较小,但我发现使用文本更容易。 如果您正在执行每个用户盐(每个密码不同的盐)则将盐+哈希作为单个组合字符串存储更容易。
大小取决于哈希算法。 MD5的输出始终为16个字节,SHA1始终为20个字节。 SHA-256和SHA-512分别为32和64字节。 如果您使用文本编码,则根据编码方法需要稍微更多的存储空间。 我倾向于使用Base64,因为存储相对便宜。 Base64将需要大约33%更大的字段。
如果您有每个用户盐,则还需要哈希的空间。 将所有内容放在一起,64位盐+ SHA1哈希(160位)base64编码需要40个字符,因此我将其存储为char(40)。
最后,如果您想正确完成操作,您不应该使用单个哈希,而应该使用RBKDF2之类的密钥推导函数。 SHA1和MD5哈希速度非常快。 单线程应用程序甚至可以每秒哈希30K至50K个密码,在四核机器上高达200K个密码每秒。 GPU可以每秒哈希100倍到1000倍的密码。 有了这样的速度,暴力攻击变成了可接受的入侵方法。 RBKDF2允许您指定迭代次数以微调哈希速度。 重点不是将系统拖垮,而是选择一定数量的迭代次数,以限制哈希吞吐量的上限(例如每秒500个哈希)。 未来证明的方法是在密码字段中包含迭代次数(迭代+盐+哈希)。 这将允许在未来增加迭代次数,以跟上更强大的处理器。 要更具灵活性,请使用varchar以允许未来可能更大/替代哈希。
.Net实现是RFC2892DeriveByteshttp://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes.aspx

2
如果您正在使用ASP.Net,可以使用内置的成员资格API。它支持许多存储选项,包括单向哈希、双向加密、md5+盐等。有关更多信息,请参见http://www.asp.net/learn/security
如果您不需要太复杂的东西,这对网站非常有用。
如果您没有使用ASP.Net,这里是4guys和codeproject的一些文章链接。

https://web.archive.org/web/20210519000117/http://aspnet.4guysfromrolla.com/articles/081705-1.aspx https://web.archive.org/web/20210510025422/http://aspnet.4guysfromrolla.com/articles/103002-1.aspx http://www.codeproject.com/KB/security/SimpleEncryption.aspx


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