最近有朋友在使劲研究如何不使用 HTTPS 的情况下保护用户密码安全。暂且不说研究过程,但结论是要保障安全必须后端参与,使用非对称加密算法 —— 如此一来,不如直接用 HTTPS 更简单便捷有保障。使用免费 SSL 证书,一年一换,运维稍微麻烦一点,访问稍微慢一点(证书认证过程好像会慢一些),但至少是专业的,比自己研究的没经过大量检验的算法靠谱多了。
假设已经做过必要的安全防范,目前唯一需要解决的问题是保障用户密码在 HTTP 的明文传输过程中不被窃取。为达此目的,研究过程如下:
对称算法加密密码
如果说,密码在明文传输过程中存在风险,最直接的解决办法是让密码不再是原来的样子。使用一个 KEY 来加密密码,再将加密后的结果传输到后台,由后台解密使用。
看起来确实起作用,如果窃取到加密后的密码数据,没有 KEY 是不能解密出来的。然而,KEY 要用于前端加密,就一定会存在于前端的某个地方,而前端的所有资源都是用户可以获取并分析的,甚至可以使用浏览器的开发者工具通过各种调试手段来分析获取。所以 KEY 本身并不能安全保存,安全性被打破。
此外,如果 secure_data 在传输过程被窃取,是可以重复使用的(因为 KEY 不变,加密结果就不会变),这也是一个不安全因素。
结论
使用对称加密算法来加密密码的方式不能保证密钥安全,也不能保证用户密码安全,更不能保护注册/登录过程安全。
对密码进行单纯的 HASH
使用对称加密,在密文和密钥都被窃取的时候,可以解出密码原文,对用户在其他系统中(可能使用了相同密码)造成威胁。为此,我们可以使用不可逆的 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
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 的问题;
- 这个 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 主要就是采用的非对称加密算法来保证数据传输安全。为了防止中间人攻击还引入了公信机构(受信任的证书中心)。但是,这个安全过程的参与方,包括 HTTPS 使用的安全协议(SSL 和 TSL 等)、服务器、公信机构、用户自己等,都可能成为整个安全过程的短板。
作为专业保障 HTTP 传输安全的 HTTPS 协议都随时在曝漏洞,请问,你又哪里来的信心凭一已之力用自己的安全算法实现来代替 HTTPS?如果没有足够的能力,还是安心用 HTTPS 吧!