0x01 PoW 算力验证
自黄牛、羊毛党诞生以来,少不了人机对抗,区分正常流量和机器流量、防止接口滥用一直是人机验证的核心。除了验证码这种大家都能看得见的验证码以外,还有一个无形的验证码 —— PoW 算力验证。
PoW(Proof-of-Work)算力验证也叫工作量证明,最多的应用是在区块链领域,是比特币的底层共识模型。在以太坊、比特币这种去中心化区块链的生态系统中,任何加密交易都需要通过共识机制来验证真实性,这样才能成为链上的一部分。PoW 具有高难度的协议程序代码,既可以使去中心化系统中的节点安全运作,又能保证区块链网络免受黑客行为的恶意攻击。
0x02 原理
首先思考一个问题,为什么需要工作量证明?
在区块链中,工作量大致可以等同于计算量,你所消耗的计算量就是这段时间的贡献,或者叫做工作量。
那又出现了一个新问题,为什么不用其他指标去证明?日常生活中,我们经常会用时间去证明你的工作量,但现在需要得到计算机的工作量,那计算机也有很多可以作伪的方法。最简单的,使用sleep(1000)
,这样时间就被我消耗了,但我只是仅仅做了一个sleep
,什么工作都没做,因此这个并不好使。
以抢票举例,黄牛在电脑上安装了100个手机虚拟系统,每个虚拟机都在使用某网站进行抢票,简单点说就是一个设备使用多个虚拟系统对一个接口进行访问,接口需要识别,这个请求是人还是机器,高频且持续的机器流量会导致正常用户无法使用,带来大量的经济损失和客户流失。
有什么函数,是无法用公式推测的?也就是说,必须老老实实运行函数,才能得出结果。而找不到一种更快的算法,也能算出相同的结果。
单向散列函数就是个好选择。让用户不断的计算一个哈希值,这会消耗算力,一个设备就算开100台设备,它整个机器的算力是固定的,是无法改变的,哈希计算的结果也是固定的,这就无法作伪。服务端只需要很少的资源消耗就可以判断对方是否存在工作量。
1 | res = MD5(x) |
当然,上面这个计算短时间是无法完成的,总不能让用户买票的时候一直等待结果算出来吧,这会很慢。因此这种算法进行了优化,只需要计算前几位就可以。
1 | res = Hash(x) |
为了防止答案不重复利用,有时候会添加盐。盐可以使用随机数,也可以提取一些信息,比如标题,内容等等。
1 | res = Hash(x, salt) |
往往这些是用户打开界面时,在后台就会接收“问题”开始计算,等到用户提交的时候,结果已经计算完成了,所以一般没有直观的感受,但对于机器就不一样了,它们短时间高频访问接口,会导致大量的哈希计算。
0x03 应用
市面上已经有很多成熟的 Hashcash 了,例如 https://wordpress.org/plugins/hashcash/ 和 https://hashcash.io/。
在验证码领域,极验也使用了相关算法提高验证码安全性。
极验验证码的工作量证明的业务流程中,加载验证资源时,极验服务器下发 PoW 参数到客户端,参数中包含 PoW 的计算难度、哈希运算方法、业务流水号等关键部分,客户端在验证时除了需要完成验证码的答案还需要额外计算 PoW 结果。客户端根据下发的参数,凭借满足条件的随机字符串进行哈希运算,通过哈希运算的不可预测性和随机字符串的随机性进行多次运算最终得出符合条件的结果,最后将结果提交到极验服务端进行校验,答案正确方可通过验证。
因正常用户的客户端一次只需通过一个验证码,其请求量少,相应的计算也很少,平均一个字符串的平均查找的时间在数十毫秒,这对正常的客户端的影响可忽略不计,但是对于恶意的客户端他们必须多花费 CPU 找到相应的串来通过检测,这样就可以限制恶意客户端的行为。
验证ID、流水号、消息生成时间这些都可以作为盐去使用,由后端直接下发,无法篡改,避免了预计算的行为。
0x04 总结
PoW 算力验证真正的目的不是识别机器和正常流量,而是防止接口在短时间被大量滥用、刷接口数据等。当然,大部分情况也不会单独使用 PoW 去做拦截,也会配合其他的安全策略进行拦截,这样就可以充分提高验证码安全性。