加盐密码保存的最通用方法是?
加盐密码保存的方法小弟也略有所闻,可是这盐究竟是怎么加捏
比如:sha1( sha1(密码).sha1(盐) )sha1( 密码.sha1(盐) )sha1(密码.盐)
这几种方式应该怎马选捏,还是说都不好还有,这盐捏,有神马要求吗,随机神马的大大请指点~~~这里顺便说一下小弟对加盐这种方式的理解,如果理解有误请大大指点(小弟.....证明我确实不是女的,虽然我的id很容易让人误解....希望大大们木有失望)就是说本来md5,sha这种哈希散列的方式基本上是不太容易逆推的可是人类吧,都比较容易选择相似的字符串,这样可怕的黑客们就跑出来一个彩虹表,基本上就让直接md5加密的方式废掉了那么加盐的方式其实主要就是让我们最终输入到md5函数的这个输入值,尽量的随机,由于输入足够随机,那么理论上的彩虹表会灰常灰常打大,黑客们跑不出来啦,结果就是:加盐是比较安全的方法是这个样子吗?
刚写了一篇密码安全相关的文章,搬过来了:
原文地址:即使被拖库,也可以保证密码不泄露
另外一篇:设计安全的账号系统的正确姿势首先,我们明确一下安全加密方案的终极目标:
即使在数据被拖库,代码被泄露,请求被劫持的情况下,也能保障用户的密码不被泄露。
说具体一些,我们理想中的绝对安全的系统大概是这样的:
- 首先保障数据很难被拖库。
- 即使数据被拖库,攻击者也无法从中破解出用户的密码。
- 即使数据被拖库,攻击者也无法伪造登录请求通过验证。
- 即使数据被拖库,攻击者劫持了用户的请求数据,也无法破解出用户的密码。
如何保障数据不被拖库,这里就不展开讲了。首先我们来说说密码加密。现在应该很少系统会直接保存用户的密码了吧,至少也是会计算密码的 md5 后保存。md5 这种不可逆的加密方法理论上已经很安全了,但是随着彩虹表的出现,使得大量长度不够的密码可以直接从彩虹表里反推出来。
所以,只对密码进行 md5 加密是肯定不够的。聪明的程序员想出了个办法,即使用户的密码很短,只要我在他的短密码后面加上一段很长的字符,再计算 md5 ,那反推出原始密码就变得非常困难了。加上的这段长字符,我们称为盐(Salt),通过这种方式加密的结果,我们称为 加盐 Hash 。比如:
上一篇我们讲过,常用的哈希函数中,SHA-256、SHA-512 会比 md5 更安全,更难破解,出于更高安全性的考虑,我的这个方案中,会使用 SHA-512 代替 md5 。
通过上面的加盐哈希运算,即使攻击者拿到了最终结果,也很难反推出原始的密码。不能反推,但可以正着推,假设攻击者将 salt 值也拿到了,那么他可以枚举遍历所有 6 位数的简单密码,加盐哈希,计算出一个结果对照表,从而破解出简单的密码。这就是通常所说的暴力破解。
为了应对暴力破解,我使用了加盐的慢哈希。慢哈希是指执行这个哈希函数非常慢,这样暴力破解需要枚举遍历所有可能结果时,就需要花上非常非常长的时间。比如:bcrypt 就是这样一个慢哈希函数:
通过调整 cost 参数,可以调整该函数慢到什么程度。假设让 bcrypt 计算一次需要 0.5 秒,遍历 6 位的简单密码,需要的时间为:((26 * 2 + 10)^6) / 2 秒,约 900 年。
好了,有了上面的基础,来看看我的最终解决方案:
上图里有很多细节,我分阶段来讲:
1. 协商密钥
基于非对称加密的密钥协商算法,可以在通信内容完全被公开的情况下,双方协商出一个只有双方才知道的密钥,然后使用该密钥进行对称加密传输数据。比如图中所用的 ECDH 密钥协商。
2. 请求 Salt
双方协商出一个密钥 SharedKey 之后,就可以使用 SharedKey 作为 AES 对称加密的密钥进行通信,客户端传给服务端自己的公钥 A ,以及加密了的用户ID(uid)。服务端从数据库中查找到该 uid 对于的 Salt1 和 Salt2 ,然后再加密返回给客户端。
注意,服务端保存的 Salt1 和 Salt2 最好和用户数据分开存储,存到其他服务器的数据库里,这样即使被 SQL 注入,想要获得 Salt1 和 Salt2 也会非常困难。
3. 验证密码
这是最重要的一步了。客户端拿到 Salt1 和 Salt2 之后,可以计算出两个加盐哈希:
SaltHash1 = bcrypt(SHA512(password), uid + salt1, 10)
SaltHash2 = SHA512(SaltHash1 + uid + salt2)
使用 SaltHash2 做为 AES 密钥,加密包括 uid,time,SaltHash1,RandKey 等内容传输给服务端:
Ticket = AES(SaltHash2, uid + time + SaltHash1 + RandKey)
AES(SharedKey, Ticket)
服务端使用 SharedKey 解密出 Ticket 之后,再从数据库中找到该 uid 对应的 SaltHash2 ,解密 Ticket ,得到 SaltHash1 ,使用 SaltHash1 重新计算 SaltHash2 看是否和数据库中的 SaltHash2 一致,从而验证密码是否正确。
校验两个哈希值是否相等时,使用时间恒定的比较函数,防止试探性攻击。
time 用于记录数据包发送的时间,用来防止录制回放攻击。
4. 加密传输
密码验证通过后,服务端生成一个随机的临时密钥 TempKey(使用安全的随机函数),并使用 RandKey 做为密钥,传输给客户端。之后双方的数据交互都通过 TempKey 作为 AES 密钥进行加密。
假设被拖库了
以上就是整个加密传输、存储的全过程。我们来假设几种攻击场景:
假设数据被拖库了,密码会泄露吗?
数据库中的 Salt1 ,Salt2 , SaltHash2 暴露了,想从 SaltHash2 直接反解出原始密码几乎是不可能的事情。假设数据被拖库了,攻击者能不能伪造登录请求通过验证?
攻击者在生成 Ticket 时,需要 SaltHash1 ,但由于并不知道密码,所以无法计算出 SaltHash1 ,又无法从 SaltHash2 反推 SaltHash1 ,所以无法伪造登录请求通过验证。假设数据被拖库了,攻击者使用中间人攻击,劫持了用户的请求,密码会被泄露吗?
中间人拥有真实服务器所有的数据,仿冒了真实的 Server ,因此,他可以解密出 Ticket 中的 SaltHash1 ,但是 SaltHash1 是无法解密出原始密码的。所以,密码也不会被泄露。
但是,中间人攻击可以获取到最后的 TempKey ,从而能监听后续的所有通信过程。这是很难解决的问题,因为在服务端所有东西都暴露的情况下,中间人假设可以劫持用户数据,仿冒真实 Server , 是很难和真实的 Server 区分开的。解决的方法也许只有防止被中间人攻击,保证 Server 的公钥在客户端不被篡改。
假设攻击已经进展到了这样的程度,还有办法补救吗?有。由于攻击者只能监听用户的登录过程,并不知道真实的密码。所以,只需要在服务端对 Salt2 进行升级,即可生成新的 SaltHash2 ,从而让攻击者所有攻击失效。
具体是这样的:用户正常的登录,服务端验证通过后,生成新的 Salt2 ,然后根据传过来的 SaltHash1 重新计算了 SaltHash2 存入数据库。下次用户再次登录时,获取到的是新的 Salt2 ,密码没有变,同样能登录,攻击者之前拖库的那份数据也失效了。
Q A
使用 bcrypt 慢哈希函数,服务端应对大量的用户登录请求,性能承受的了吗?
该方案中,细心一点会注意到, bcrypt 只是在客户端进行运算的,服务端是直接拿到客户端运算好的结果( SaltHash1 )后 SHA-512 计算结果进行验证的。所以,把性能压力分摊到了各个客户端。
为什么要使用两个 Salt 值?
使用两个 Salt 值,是为了防止拖库后,劫持了用户请求后将密码破解出来。只有拥有密码的用户,才能用第一个 Salt 值计算出 SaltHash1 ,并且不能反推回原始密码。第二个 Salt 值可以加大被拖库后直接解密出 SaltHash1 的难度。
为什么要动态请求 Salt1 和 Salt2 ?
Salt 值直接写在客户端肯定不好,而且写死了要修改还得升级客户端。动态请求 Salt 值,还可以实现不升级客户端的情况下,对密码进行动态升级:服务端可定期更换 Salt2 ,重新计算 SaltHash2 ,让攻击者即使拖了一次数据也很快处于失效状态。
数据库都已经全被拖走了,密码不泄露还有什么意义呢?
其实是有意义的,正如刚刚提到的升级 Salt2 的补救方案,用户可以在完全不知情的情况下,不需要修改密码就升级了账号体系。同时,保护好用户的密码,不被攻击者拿去撞别家网站的库,也是一份责任。
欢迎大家针对本文的方案进行讨论,如有不实或者考虑不周的地方,请尽情指出。或者有更好的建议或意见,欢迎交流!
简单回答一下:
- ……这几种方式应该怎马选捏,还是说都不好……都好啊,这没有固定的方式的,不过你加的盐都不是随机的,只要我愿意花时间去生成你的盐的彩虹表,跟普通加密没区别。我曾经开发的应用加盐方式比较变态,重点是随机盐,供你参考。假设密码是abcdef,将注册日期的天数比方说是14号,那就是14,还有用户的账户名,假设是ABCDE组成盐加进去,变成「ab+14+cd+ABCDE+ef」的字符串,然后再将这个字符串取MD5存数据库。
- 你对加盐的理解有误,对彩虹表的理解也有误。MD5和SHA1的散列函数是没办法逆推的。假设有个函数 y = f(x),y1 = f(x1) = f(x2),那么破解者只知道y1和函数 f ( ) 的情况下是没办法知道x 是x1还是x2的(MD5就是一种散列函数,而对应一个因变量 y,有多个自变量x的存在),所以是没办法逆推的,你的说法有误;破解者拿到了加密后的密码 y,然后根据自己的彩虹表找出可能通过MD5(或者其他散列函数)计算以后能够得到y的密码x2,x2不一定是用户本来的密码,这是一对弱碰撞。但不需要知道,破解者就可以登录这个用户的帐号了,因为散列后得到的y是无误的。以上是MD5/SHA1已经不安全的缘故,不是因为生日、或者规律什么的。原来你的密码是「abcdefg」,破解者拿到了「@#$uvwxyz%^」。计算查表(MD5)得到「hijklmn」也是可以得到该密文的,那这个加密就没用了。如果你的加盐方式是在密码后面加个「fg」,你的密码是「abcde」,那么跟前述一样,破解者不知道你的算法,试试普通的MD5,查表计算发现「hijklmn」经过MD5以后也是一样的密文,这个时候尝试登录发现失败了,原因是,实际上经过MD5计算的是「hijklmnfg」。这给破解者的难题是,服务器加密密码的算法,即你是怎么加盐的。固定盐的坏处:破解者已经得到你的数据库了,难道还没办法得到你的源代码吗?得到你的源代码以后也就知道你是如何加盐的,哦,原来你是在密码后面加上「fg」,那好,我再改善下我的表,我也跟着你加「fg」,重新生成一份密码表。接下来的工作跟之前一样了。随机盐的话,即使对方知道算法,也很难搞,因为对应于每一份盐,都得花时间空间去生成对应的密码表,也就是说一个密码表只能破解一个用户的密码,这还是建立在已经得到数据库和加密算法的情况下,成本瞬间高了几个数量级。
- ……证明我确实不是女的,虽然我的id很容易让人误解……这ID真的很女。
有什么不对请路过的专业人员指教。
Secure Salted Password Hashing
乌云的翻译:
加盐能防御两方面:1.对于外部的破解,如彩虹表等。当salt的长度10位以上,其实就很难破解了,实际应用中越长越好;2.防御内部管理人员获取用户密码。比如DBA,运维人员自己注册个账号,密码设置为123456,数据库里SHA1加密后的密码字段假设为XXX,那么他只要select一下,user表里有没有密码为XXX的,如果有,那么这些用户的密码设置都是123456。但是加盐之后,因为salt是随机生成的,所以即使有100位用户的密码是123456,那么他们的SHA1(123456+salt)也是不一样的(salt位数越长,几率越小,只存在理论上的可能性,而且SHA系列的加密无法逆推)。DBA或者运维就很难从后台的数据表里获得user的密码。实际应用中,加盐可能是1s2a3l4t56这样的格式,至于具体的,自己去定义。salt随机数的生成,最好是自己去写,不要用网上google来的。字符备选库里用26的字母大小写+10位数字+一些符号就可以了。
楼上答案很全面了,不过想指出的是楼上所指出的加盐方式也不是非常安全,因为加的盐比较规则。当然抵挡大部分的攻击足够了,在系统没有足够价值之前,也不会有人有兴趣去尝试攻击了,因为得不偿失。
更好的盐值建议使用基于加密的伪随机数生成器,因为不会很容易被猜到。另外一个好的标准的是:盐值至少和哈希函数的输出一样长。(足够大的长度提供了足够多的盐值)并且对于每个用户的每个密码,盐值都应该是独一无二的。每当有新用户注册或者修改密码,都应该使用新的盐值进行加密。更全面的教程:加盐密码哈希:如何正确使用很好的一篇博文。推荐你没提到的想法,就是sha1(盐.密码)然后取前32位,盐不要用单独的字段存储,用生成时间做盐
为什么?
节省存储空间拿到库的人会认为你没加盐拿到库的人会认为你用md5盐要求的是随机性,采用系统随机数即可,不要用什么时间、IP等任何有语意的信息。如果采用固定盐,在数学上等于没加盐,因为无法通过频域分析,而这时密码分析领域的最初级分析。至于盐如何||密码,这些模式可能也会暗藏很多意外的问题,主要是当前hash算法都是全状态输出,包括md5/sha1/sha2。没分析清楚的时候可以前后各加一个随机数。以后sha3将会是海绵函数体系,对于模式的要求很少。
盐 =取 md5(创建时间+固定字符串) 中7位。 这样盐就不用储存了
使用byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES)没烦恼
这几种方法都可以,加盐最好不要用固定的字符串,也不要太短,比如用户名加盐就比较安全。如果用一些开源的自带加盐算法也是容易被一些类似http://www.ttmd5.com等的在线解密网站破解的。
https防止中间人pbkdf2安全裤防脱裤
我也加个自己的理解,有问题请指出。
密码P经过加盐sa然后哈希得到psh,psh在服务器再进行哈希psh_real,和盐sa一起保存在服务器。验证过程:客户端: 用户密码用协商的随机数作为密钥来加密得到p_send.服务器:服务器收到p_send,解密得到密码P,将P加盐sa然后哈希得到psh,将psh做服务器的哈希得到psh_hash,检查psh_hash和psh_hash_real是否一致,一致则验证通过。(这里的随机数是每次客户端和服务器之间协商得到的,验证码是一种(虽然验证码作为anti-robot比较合理),保存的psh和盐值是注册的时候客户端发送过去的,所以注册一般都用加密链接https)。加盐的重要性:
攻击者拿到p_send没有意义,因为这里有个随机数作为密钥来加密,每次都不一样,重放攻击失效。 攻击者攻破服务器,拿到psh_real,如果没有加盐,攻击者通过彩虹表得到psh_real两次"反哈希"的结果,即密码明文或等价明文。得到密码明文就可以伪造成客户端加密为p_send且p_send每次都可用。 但是由于是加盐的,攻击者不知道盐值sa,所以两次哈希之后得到无效的密码明文或等价明文(因为不知道原密码被盐值如何扰动)。即使是固定加盐,也大大增加了彩虹表建立的代价(即针对单个hash的字典攻击和暴力攻击还是可能成功的),如果每个用户使用不同的随机加盐方式,彩虹表攻击就几乎失效了(想想那得是多么庞大的表,两个用户使用同一个密码也会生成不同的哈希值,即加大随机,减少碰撞)。所以没有办法得到用户的密码。 而得不到用户的密码,就没法让服务器成功验证了。歪个题,加盐是一种比较古老的防止撞库的方法,而基于零知识证明的认证方案是非常安全,能抵抗穷举攻击(更安全点说,明,密文安全)的方案,运行效率也不低。
推薦閱讀:
※數學上:能否實現在文件中保存文件自己的MD5值?
※如何破解經過 MD5 演算法處理的信息?
※怎樣在 Mac 上查看文件的 MD5 值?
※md5 編碼可以反編碼出來么?就是已經知道生成的 md5 編碼,反推源文件
※MD5校驗會有極低的碰撞率,那麼經過MD5之後產生的16個位元組再次進行MD5運算會不會進一步降低碰撞率?