【文章来自:http://blog.coderzh.com/2016/01/10/a-password-security-design-example/】
在前一篇文章《设计安全的账号系统的正确姿势》中,主要提出了一些设计的方法和思路,并没有给出一个更加具体的,可以实施的安全加密方案。经过我仔细的思考并了解了目前一些方案后,我设计了一个自认为还比较安全的安全加密方案。本文主要就是讲述这个方案,非常欢迎和期待有读者一起来讨论。
首先,我们明确一下安全加密方案的终极目标:
即使在数据被拖库,代码被泄露,请求被劫持的情况下,也能保障用户的密码不被泄露。
说具体一些,我们理想中的绝对安全的系统大概是这样的:
- 首先保障数据很难被拖库。
- 即使数据被拖库,攻击者也无法从中破解出用户的密码。
- 即使数据被拖库,攻击者也无法伪造登录请求通过验证。
- 即使数据被拖库,攻击者劫持了用户的请求数据,也无法破解出用户的密码。
如何保障数据不被拖库,这里就不展开讲了。首先我们来说说密码加密。现在应该很少系统会直接保存用户的密码了吧,至少也是会计算密码的 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 的补救方案,用户可以在完全不知情的情况下,不需要修改密码就升级了账号体系。同时,保护好用户的密码,不被攻击者拿去撞别家网站的库,也是一份责任。
相关推荐
### 密码库泄露的意义 1. **对个人的影响**:密码泄露可能导致用户的个人信息被非法获取,进而引发如身份盗用、资金损失等问题。在本案例中,“一看还这是大吃一惊,因为自己的密码也被公开”这一描述表明了用户在...
从提供的信息来看,这似乎是一份包含用户名、密码以及邮箱地址的数据列表,这些数据可能来源于一个被称为“天涯 scdn 密码库”的资料库。根据描述,“庞大的密码库可用于统计分析”,这意味着这份资料可能是用于安全...
SQL注入是一种常见的网络安全威胁,它发生在应用程序未能充分验证或过滤用户输入的数据时,导致恶意SQL代码被嵌入到查询中并被执行...理解攻击者如何利用SQL注入进行拖库,可以帮助我们更好地保护系统,避免数据泄露。
5. **备份与恢复策略**:密码库的备份需要谨慎处理,因为泄露可能会导致重大损失。备份应加密存储,并且有严格的访问策略。 6. **合规性**:在处理个人数据时,必须遵循相关的法规,如GDPR(欧洲通用数据保护条例)...
即使主密码被泄露,攻击者仍然需要第二重验证才能访问密码库。 8. **开放源码**:“mystika-master”可能是一个开源项目,这意味着它的源代码是公开的,可供开发者审查和改进。开源密码库的好处是透明度高,社区...
标题中的“1400万通用密码库”指的是一个包含了大约1400万个常见密码的集合,这个数据集通常被安全研究人员、密码分析人员以及网络安全教育者使用,以研究和评估用户密码的安全性。这样的密码库是通过对互联网上泄露...
加密是保护隐私泄露的根本办法,很多满足应用需求的密码机制也已经不断被提出。但是虽然密码算法本身具有可证明安全性,但是在应用中产生的信息泄露,依然使其容易被破解。密码应用正面临的新挑战,保护隐私迫在眉睫...
此外,为了避免密码泄露,可以使用哈希函数和盐值对密码进行加密存储,即使数据库被攻破,也不能直接获取到原始密码。 总的来说,"可以不刷新页面检测用户输入的密码强度"这一功能是通过JavaScript实现实时反馈,...
在Android应用开发中,保证后台服务不被系统自动杀死是一项重要的技术挑战,尤其是在资源有限的移动设备上。本文将深入探讨如何实现这一目标,主要分为提高进程优先级、避免进程被杀死以及进程被杀死后的拉活策略。 ...
这类设备通常更为安全,因为即使USB设备丢失,数据也无法轻易被他人获取。 为了增强USB设备的安全性,还应结合其他安全策略,比如使用复杂且强度高的密码,定期更改密码,以及限制非授权的USB设备接入。同时,对于...
动态库,也称为共享库或动态链接库,是程序运行时加载的代码集合,可以为多个应用程序提供共同的服务,包括密码加密功能。在这个压缩包文件中,我们很可能会找到一个用于密码加密的动态库,以及相关的操作说明,帮助...
使用单向散列加密、对称加密和非对称加密算法,可以保护用户密码的安全性,即使数据库被黑客拖库,也不会泄漏用户密码。但是,我们也需要注意,在实践中,使用加密算法加密,还需要在计算过程中加点“盐”,以增加...
在这种情况下,设置密码的VI在版本库中应被视为二进制文件,因为版本控制系统无法跟踪密码的更改。 - 对于需要共享但又希望保护的部分,可以考虑使用子VI和封装技术,将敏感部分封装在单独的子VI中,并仅对这些子VI...
但允许自定义字符意味着用户可以指定哪些字符被包含在生成的密码中,这可能是为了符合某些特定网站或服务的密码规则,或者是为了排除某些不常用或易混淆的字符。 从标签中再次强调了这两个关键功能,可见它们是该...
易语言编译出来的EXE文件,用记事本打开后CTRL+F搜索可以找到里面的明文密码和一些明文的数据,这样很不安全,这不是易语言的问题,其他编程语言也会有,这个小工具很简单,对刚入门的初学者有点用,高手没必要下载...
密码需要在必要时间或次数(最少 5 次)内不循环使用,且账户曾用的各个密码之间应当是没有直接联系的,以保证不可由以前的密码推知现在的密码。密码不应当取有意义的词语或其他符号,如使用者的姓名、生日或其它...
即使因工作需要必须转告密码,也需经过上级领导的批准,并且在使用完毕后及时更改密码,从而确保密码不被滥用。 在应急响应方面,一旦发现密码可能泄露或存在黑客入侵的迹象,系统管理员应立即向IT部门主管报告。...
在实际应用中,可以结合后端验证,确保即使前端检查通过,后端也能再次确认密码的安全性。同时,为了保护用户隐私,密码不应在明文状态下在网络中传输,应使用加密技术进行处理。 总之,JavaScript密码强度检测是...
此外,对于开发者来说,理解Go语言的内存管理和并发模型也非常重要,因为这些特性可以保证程序在生成大量密码时的性能和安全性。Go的垃圾回收机制有助于防止内存泄漏,而goroutines和channels则允许并发执行,提高...
在Rust编程语言中,实现阈值密码库可以帮助开发人员构建安全的应用程序。Rust以其内存安全和高性能的特点,成为编写密码学软件的理想选择。"Rust中的阈值密码库"很可能是一个开源项目,提供了在Rust中实施阈值密码...