最近有朋友在使劲研究如何不使用 HTTPS 的情况下保护用户密码安全。暂且不说研究过程,但结论是要保障安全必须后端参与,使用非对称加密算法 —— 如此一来,不如直接用 HTTPS 更简单便捷有保障。使用免费 SSL 证书,一年一换,运维稍微麻烦一点,访问稍微慢一点(证书认证过程好像会慢一些),但至少是专业的,比自己研究的没经过大量检验的算法靠谱多了。
假设已经做过必要的安全防范,目前唯一需要解决的问题是保障用户密码在 HTTP 的明文传输过程中不被窃取。为达此目的,研究过程如下:
如果说,密码在明文传输过程中存在风险,最直接的解决办法是让密码不再是原来的样子。使用一个 KEY
来加密密码,再将加密后的结果传输到后台,由后台解密使用。
看起来确实起作用,如果窃取到加密后的密码数据,没有 KEY
是不能解密出来的。然而,KEY
要用于前端加密,就一定会存在于前端的某个地方,而前端的所有资源都是用户可以获取并分析的,甚至可以使用浏览器的开发者工具通过各种调试手段来分析获取。所以 KEY
本身并不能安全保存,安全性被打破。
此外,如果 secure_data
在传输过程被窃取,是可以重复使用的(因为 KEY
不变,加密结果就不会变),这也是一个不安全因素。
结论 📚使用对称加密算法来加密密码的方式不能保证密钥安全,也不能保证用户密码安全,更不能保护注册/登录过程安全。
使用对称加密,在密文和密钥都被窃取的时候,可以解出密码原文,对用户在其他系统中(可能使用了相同密码)造成威胁。为此,我们可以使用不可逆的 HASH 算法来处理密码,只要前后端计算方式相同,不需要解密出密码原文,直接使用 HASH 结果就行。
用户注册时,假设我们有原密码 my_password
,使用 MD5 计算后是 a865a7e0ddbf35fa6f6a232e0893bea4
。这个 HASH 送到后端之后,后端是不能从 HASH 反算出原密码的,所以后端只能直接保存这个 HASH。
那么验证的时候,同样将原密码计算成 HASH a865a7e0ddbf35fa6f6a232e0893bea4
,传输到后端,后端拿它和保存的数据进行对比,相同则验证通过。
有问题吗?当然有,你看 ——
使用 Hash 的过程
不使用 HASH 的过程
从传输过程开始,Hash 前的过程和 Hash 后的过程并没有区别。对于偷窃者来说,无所谓是拿到的 my_password
还是 a865a7...
,只要把它送到后端,就能成功登录。
所以这种做法并不能保护用户登录!
不过密码是采用 Hash 计算过的,是不是能保护用户密码不会窃取用于尝试登录其他系统呢?也不能!
一些常见的密码,比如 123456
采用 MD5 计算后是 e10adc3949ba59abbe56e057f20f883e
,拿这个 Hash 上百度就能查出来原密码是 123456
。当然,专业人士会有专业的工具,也就是彩虹表(什么是彩虹表?查查呗!),数以 TB 计的数据,可能大部分密码都查得出来吧。
结论 📚从前端对密码进行单纯的 HASH 算法,起不到任何保护作用。
其实从后端对密码进行单纯的 HASH 加密也只能起到很弱的保护作用,几近裸奔。
HMAC 比单纯的 HASH 算法,要多一个密钥因素,可以认为是加密的 HASH。使用 HMAC 可以有效的阻碍彩虹表破解。因此使用 HMAC 保护用户密码原文。但是,如果传输的是 HMAC 计算结果,和前面的讨论同理,并不能保护登录。
但使用 HMAC 和单纯的 HASH 又有那么一点不同。假设我们已经通过其他方式进行安全的注册,后端已经保存了用户密码 my_password
,来看看下面这个登录过程:
攻击者只需要窃取 hash_result
即可仿冒登录,而 hmac_result
在传输过程中可以轻松窃取。
此外,还有一个不安全点:固化在脚本中的 secure_key
是可以从拉到客户的脚本资源中分析出来的。
结论 📚使用固定 secure key 的 HMAC 算法并不能保障登录安全,只能起到保密用户密码原文的作用。但是,由于
secure_key
很容易被拿到,所以对密码原文的保护也相对较弱。
继续寻找解决办法,我们可以想到一个改进方案:如果满足下面两个条件,上面遇到的问题似乎就能解决:
secure_key
并不是固定在脚本中,而是动态产生的,就可以解决从脚本分析获得 secure_key
的问题;secure_key
使用 1 次后立即失效,那么 hmac_result
就不能用于再一次登录,可以解决 hmac_result
被窃取复用的问题。动态 secure_key
看起来是个不错的办法,但是它应该在哪里动态产生呢?
如果在前端动态产生,就必须通知后端,传输的数据需要包含 secure_key
和 hmac_result
两部分。他们都能被窃取使用。这种状态下如果要保证 secure_key
只能使用一次,就必须要后端缓存所有用过的 secure_key
备查 —— 显然这会极大的增加后端负担。因此,不可以由前端来产生动态安全码。
用后端产生动态安全码之后可以立即缓存起来,同时传输给前端(这个过程可以前端发起请求)。前端按上述步骤对用户密码进行加密,将数据送回后端。后端检查 secure_key
在缓存中,取出来计算用于验证的 hmac_result
,同时从缓存中删除 secure_key
;如果 secure_key
不在缓存中,直接拒绝验证。
引入动态 secure_key
解决了登录期间验证用户密码的问题。
虽然数据在传输过程中仍然能被窃取,但是被窃取的数据不能重复使用,也不易破解出密码原文(可花长时间暴力破解),所以这个方案是初步可行的 —— 但要注意,它可能会受中到中间人攻击。
此外,该方案是不完整的,因为它只能解决验证密码(登录)的问题,不能解决保存密码(注册)的问题 —— 注意到后端是直接从数据库加载的 my_password
,这意味着在注册时需要传入密码原文或者可解密出密码原文的密文 —— 这在之前的研究中都还没找到办法。
结论 📚后端产生一次性使用的动态
secure_key
可以保障登录安全,但不能解决注册安全。而且该方案需要注意防护中间人攻击。注意:需要后端参与
到目前为止,我们总算找到一个前端登录勉强可用的安全方案。接下来还要研究用户注册时该怎么办。
注册时需要传输可解密的密文,而且在前端不能找到可用于解密的密钥 —— 这个场景非常符合非对称加密的应用场景 —— 前端使用公钥加密,后端使用私钥解密,问题就能得到完美解决:
结论 📚使用非对称加密算法,可以保护用户密码安全,也可以保护登录过程安全。但是要注意中间人攻击。
注意:需要后端参与
到此为止,我们已经非常接近 HTTPS 了。HTTPS 主要就是采用的非对称加密算法来保证数据传输安全。为了防止中间人攻击还引入了公信机构(受信任的证书中心)。但是,这个安全过程的参与方,包括 HTTPS 使用的安全协议(SSL 和 TSL 等)、服务器、公信机构、用户自己等,都可能成为整个安全过程的短板。
作为专业保障 HTTP 传输安全的 HTTPS 协议都随时在曝漏洞,请问,你又哪里来的信心凭一已之力用自己的安全算法实现来代替 HTTPS?如果没有足够的能力,还是安心用 HTTPS 吧!
请关注公众号边城客栈⇗
看完了先别走,点个赞 ⇓ 啊,赞赏 ⇘ 就更好啦!