找回密码
 骑士注册

QQ登录

微博登录

搜索
❏ 站外平台:

加盐密码哈希:如何正确使用

2014-03-20 10:13    评论: 4 收藏: 4 分享: 5    

常见问题

我应该使用什么哈希算法?

应该使用:

  • 本文末尾的PHP source code, Java source code, C# source code or the Ruby source code
  • OpenWall的Portable PHP password hashing framework
  • 任何先进的、被良好测试过的哈希加密算法,比如SHA256,SHA512,RipeMD,WHIRLPOOL,SHA3等等
  • 设计良好的密钥扩展算法,如PBKDF2bcryptscrypt
  • 安全的crypt()版本($2y$,$5$,$6$)

不要使用:

  • 过时的函数,比如MD5或SHA1
  • 不安全的crypt()版本($1$,$2$,$2x$,$3$)
  • 任何你自己设计的加密算法。只应该使用那些在公开领域中的,并且被密码学家完整测试过的技术

尽管还没有一种针对MD5或SHA1非常效率的攻击手段,但是它们太古老也被广泛地认为不足以胜任存储密码的工作(某种程度上甚至是错误的),因此我也不推荐使用它们。但是有个例外,PBKDF2中频繁地使用了SHA1作为它底层的哈希函数。

当用户忘记密码的时候,怎样进行重置?

我个人的观点是,当前所有广泛使用的密码重置机制都是不安全的。如果你对安全性有极高的要求,比如一个加密服务,那么不要允许用户重置密码。
大多数网站向那些忘记密码的用户发送电子邮件来进行身份认证。首先,需要随机生成一个一次性的令牌,它直接关联到用户的账户。然后将这个令牌混入一个重置密码的链接中,发送到用户的电子邮箱。最后当用户点击这个包含有效令牌的链接时,提示他们可以设置新的密码。要确保这个令牌只对一个账户有效,以防攻击者从邮箱获取到令牌后,用来重置其他用户的密码。

令牌必须在15分钟内使用,并且一旦被使用就立即失效。当用户重新请求令牌时,或用户登录成功时(说明他还记得密码),使原令牌失效也是一个好做法。如果一个令牌始终不过期,那么它一直可以用于入侵用户的帐号。电子邮件(SMTP)是一个纯文本协议,并且网络上有很多恶意路由在截取邮件信息。在用户修改密码后,那些包含重置密码链接的邮件在很长一段时间内依然缺乏保护。因此应该尽早使令牌过期,降低把用户信息暴露给攻击者的可能。

攻击者是可以篡改令牌的,所以不要把账户信息和失效时间存储在里面。这些信息应该以不可猜解的二进制形式存在,并且只用来识别数据库中某条用户的记录。

永远不要通过电子邮件向用户发送新密码,同时也记得在用户重置密码的时候随机生成一个新的盐值用于加密,不要重复使用之前密码的那个盐值。

当账户数据库被泄漏或入侵时,应该怎么做?

你首先需要做的,是查看系统被暴露到什么程度了,然后修复这个攻击者利用的漏洞。如果你没有应对入侵的经验,我强烈推荐雇一个第三方安全机构来做这件事。

将一个漏洞精心掩盖期待没有人能注意到,是否听起来很省事而又诱人呢?但是这样只会让你显得更糟糕,因为你在用户不知情的情况下,将他们的密码和个人信息暴露在危险之中。即使用户还无法理解到底发生了什么,你也应该尽快履行告知的义务。比如在首页放置一个链接,指向对此问题更详细的说明,可能的话还可以通过电子邮件告知用户目前的情况。

向你的用户说明你是如何保护他们的密码的——最好是使用了加盐哈希——即便如此恶意黑客也能使用字典攻击和暴力攻击。设想用户可能在很多服务中使用相同的密码,攻击者会用找到的密码去尝试登录其他网站。提示你的用户应该修改所有相似的密码,不论它们被使用在哪个服务上,并且强制用户下次登录你的网站时修改密码。大部分用户会尝试将密码“修改”为和之前相同的以便记忆,你应该使用老密码的哈希值来确保用户无法这么做。

即使有加盐哈希的保护,攻击者也很可能快速破解其中一些脆弱的密码。为了减少攻击者使用的它们机会,你应该对这些密码的帐号发送认证电子邮件,直到用户修改了密码。可以参考上一个问题,其中有一些实现电子邮件认证的要点。

另外也要告诉你的用户,网站到底储存了哪些个人信息。如果你的数据库中有用户的信用卡号,你应该指导用户检查自己近期的账单,并且注销掉这张信用卡。

我应该使用什么样的密码规则?是否应该强制用户使用复杂的密码?

如果你的服务对安全性没有严格的要求,那么不要对用户进行限制。我推荐在用户输入密码的时候,页面上显示出密码强度,由用户自己决定需要多安全的密码。如果你的服务对安全有特殊的需求,那就应该强制用户输入长度至少为12个字符的密码,并且其中至少包括两个字母、两个数字和两个符号。

不要过于频繁地强制你的用户修改密码,最多6个月1次,因为那样做会使用户疲于选择一个强度足够好的密码。更好的做法是指导用户在他们感觉密码可能泄漏的时候去主动修改,并且提示用户不要把密码告诉任何人。如果这是在商业环境中,鼓励你的员工利用工作时间熟记并使用他们的密码。

如果攻击者入侵了我的数据库,他们难道不能把其中的密码哈希替换为自己的值,然后登录系统么?

当然可以,但是如果他已经入侵了你的数据库,那么很可能已经有权限访问你服务器上任何东西了,因此完全没必要登录账户去获取他想要的。对密码进行哈希加密的手段,(对网站而言)不是保护网站免受入侵,而是在入侵已经发生时保护数据库中的密码。

通过为数据库连接设置两种权限,可以防止密码哈希在遭遇注入攻击时被篡改。一种权限用于创建用户:它对用户表可读可写;另一种用于用户登录,它只能读用户表而不能写。

为什么我非得用像HMAC那种特殊的算法?为什么不能简单地把密钥混入密码?

像MD5、SHA1和SHA2这类哈希函数是基于Merkle–Damgård构造的,因此在长度扩展攻击面前非常脆弱。就是说如果已经知道一个哈希值H(X),对于任意的字符串Y,攻击者可以计算出H(pad(X) + Y)的值,而不需要知道X是多少,其中pad(X)是哈希函数的填充函数(padding function,比如MD5将数据每512bit分为一组,最后不足的将填充字节)。

在攻击者不知道密钥(key)的情况下,他仍然可以根据哈希值H(key + message)计算出H(pad(key + message) + extension)。如果这个哈希值用于身份认证,并且依靠其中的密钥来防止攻击者篡改消息,这个办法已经行不通了。因为攻击者无需知道密钥,也能构造出包含message + extension的一个有效的哈希值。

目前还不清楚攻击者能否用这个办法更快破解密码,但是由于这种攻击的出现,在密钥哈希中使用上述哈希函数已经被认为是差劲的实践了。也许某天高明的密码学家会发现一个利用长度扩展攻击的新思路,从而更快地破解密码,所以还是使用HMAC吧。

盐值应该加到密码前面还是后面?

都行,但是在一个程序中应该保持一致,以免出现互操作方面的问题。目前看来加到密码之前是比较常用的做法。

为什么本文中的代码在比较哈希值的时候,都是经过固定的时间才返回结果?

让比较过程耗费固定的时间可以保证攻击者无法对一个在线系统使用计时攻击,以此获取密码的哈希值,然后进行本地破解工作。

比较两个字节序列(字符串)的标准做法是,从第一字节开始,每个字节逐一顺序比较。只要发现某字节不相同了,就可以立即返回“假”的结果。如果遍历整个字符串也没有找到不同的字节,那么两个字符串就是相同的,并且返回“真”。这意味着比较字符串的耗时决定于两个字符串到底有多大的不同。

举个例子,使用标准的方法比较“xyzabc”和“abcxyz”,由于第一个字符就不同,不需要检查后面的内容就可以马上返回结果。相反,如果比较“aaaaaaaaaaB”和“aaaaaaaaaaZ”,比较算法就需要遍历最后一位前所有的“a”,然后才能知道它们是不相同的。

假设攻击者妄图入侵一个在线系统,并且此系统限制了每秒只能尝试一次用户认证。还假设他已经知道了密码哈希所有的参数(盐值、哈希函数的类型等等),除了密码的哈希值和密码本身(显然啊,否则还破解个什么)。如果攻击者能精确测量在线系统耗时多久去比较他猜测的密码和真实密码,那么他就能使用计时攻击获取密码的哈希值,然后进行离线破解,从而绕过系统对认证频率的限制。

首先攻击者准备256个字符串,它们的哈希值的第一字节包含了所有可能的情况。然后用它们去系统中尝试登录,并记录系统返回结果所消耗的时间,耗时最长的那个就是第一字节猜对的那个。接下来用同样的方式猜测第二字节、第三字节等等。直到攻击者获取了最够长的哈希值片段,最后只需在自己的机器上破解即可,完全不受在线系统的限制。

乍看之下在网络上进行计时攻击是不可能做到的,然而有人已经实现了,并运用到实际中了。因此本文提供的代码才使用固定的时间去比较字符串,不论它们有多相似。

“慢比较”的代码是如何工作的?

上一个问题解释了为什么“慢比较”是有必要的,现在来讲解一下代码具体是怎么实现的。

private static boolean slowEquals(byte[] a, byte[] b)
{
    int diff = a.length ^ b.length;
    for(int i = 0; i < a.length && i < b.length; i++)
    diff |= a[i] ^ b[i];
    return diff == 0;
}

代码中使用了异或运算符“^”(XOR)来比较两个整数是否相等,而不是“==”。当且仅当两位相等时,异或的结果才是0。因为0 XOR 0 = 0, 1 XOR 1 = 0, 0 XOR 1 = 1, 1 XOR 0 = 1。应用到整数中每一位就是说,当且仅当字节两个整数各位都相等,结果才是0。

代码中的第一行,比较a.length和b.length,相同的话diff是0,否则diff非0。然后使用异或比较数组中各字节,并且将结果和diff求或。如果有任何一个字节不相同,diff就会变成非0的值。因为或运算没有“置0”的功能,所以循环结束后diff是0的话只有一种可能,那就是循环前两个数组长度相等(a.length == b.length),并且数组中每一个字节都相同(每次异或的结果都非0)。
我们使用XOR而不是“==”来比较整数的原因是:“==”通常被翻译/编译/解释为带有分支的语句。例如C语言中的“diff &= a == b”可能在x86机器成被编译为如下汇编语言:

MOV EAX, [A]
CMP [B], EAX
JZ equal
JMP done
equal:
AND [VALID], 1
done:
AND [VALID], 0

其中的分支导致代码运行的时间不固定,决定于两个整数相等的程度和CPU内部的跳转预测机制(branch prediction)。

而C语言代码“diff |=a ^ b”会被编译为下面的样子,它执行的时间和两个整数是什么样的情况无关。

MOV EAX, [A]
XOR EAX, [B]
OR [DIFF], EAX

弄这么麻烦干嘛?

用户在你的网站上输入密码,说明他们相信你会保障密码的安全。如果你的数据库被黑了,又没有对用户密码加以保护,恶意黑客就可以使用这些密码去入侵用户在其他网站或服务的账户(大部分人会在各处使用相同的密码)。这不仅仅关乎你网站的安全,更关系到用户的。你需要对用户的安全负责。

PHP PBKDF2 密码哈希代码

下面是PBKDF2在PHP中一种安全的实现,你也可以在这个页面找到测试用例和基准测试的代码。

 

文章和代码由Defuse Security编写。

原文链接: Crackstation   翻译: 伯乐在线 蒋生武

译文链接: http://blog.jobbole.com/61872/

123
查看其它分页:

最新评论

我也要发表评论

来自伊朗的 Firefox 45.0|Windows 10 用户 2017-07-12 02:00 回复
hCxL9t8t83KLc2LMikRn0sMO6soyqM8f
[1]
发表于 2014-03-23 18:52 的评论:
转载怎么都不给出处的?
linux 2014-04-27 19:21 回复
给了出处了啊,在文章最后一页。这一点我们向来注意,都是原创或者辛苦翻译的,能够理解的。
Swfffffffff 2014-03-20 10:50  新浪微博网友评论 回复
hash也不安全 老师个骗纸

收藏

返回顶部

分享到微信

打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。