首先简要说明一下什么是 CAPTCHA,所谓 CAPTCHA,就是在注册网站用户时常常见到的各种各样的验证码,这种验证码的学名就叫做 CAPTCHA(Completely Automated Public Turing test to tell Computers and Robots Apart,全自动人机区分图灵测试) 。

CAPTCHA 最典型的用处就是防止机器自动请求接口,比如使用程序自动请求登录接口,试图破解用户密码。

CAPTCHA 的工作原理也很简单,以验证码为例,对于人类来说,我们很容易认出图片中的验证码,而对于机器来说,这是比较困难的事情。如果验证码的字体规整、背景纯净,那么随便 OCR 一下还能识别出图片中的文字,但如果图片背景复杂,文字又扭曲,机器就很难识别出图片中的文字了。

鉴于此,只要一个用户能够识别出图片中的文字,我们就可以认为这是一个人类,如果识别不出,那这个用户就非常有可能是机器。这就是 CAPTCHA 的基本原理。

然而,现在常见的验证码都需要用户进行交互(比如选择几张图片,或者输入图片中的数字),用户体验不够流畅。现在也有不需要用户交互的验证码,如如谷歌的 reCAPTCHA v3 和 Cloudflare 的 hCaptcha。

wCaptcha 也是一个不需要用户进行交互的,在用户填写表单的时候,可以在后台自动生成一个计算量证明,通过计算量证明,可以识别用户是一个”恶意用户“还是一个”普通用户“。

# wCaptcha 的思路

wCaptcha 要求客户端在发送请求时,必须提供一个计算量证明,这个证明在现代的 CPU 上需要计算几秒钟的时间。如果客户端没有提供正确的计算量证明,那么就认为这个客户端是一个机器人。

这看起来很反直觉,明明是用电脑才能计算出的计算量证明,为什么反而可以证明客户端不是机器人呢?wCaptcha 的目的在于限制需要大量发送请求的机器人或者爬虫,对于正常用户来说,发送请求的频率不会很频繁,花上几秒钟的时间生成一个计算量证明是可以接受的,而对于机器人或者爬虫来说,要发送大量的请求,就意味着需要提供大量的计算量证明,也就要消耗大量的 CPU 时间,这在经济上就不可接受了。

# wCaptcha 的验证过程

用户准备提交表单的时候,首先会从 wCaptcha 处获得一个随机问题(实际上就是一个随机数),然后根据这个随机问题,花费几秒钟的时间生成计算量证明,在提交表单的时候,将表单数据和计算量证明一并提交。

服务端收到请求后,对计算量证明进行验证(验证速度非常快,通常只需要要毫秒级的时间),然后根据验证结果决定是否接受客户端的请求。

# wCaptcha 的特点和局限性

wCaptcha 的计算量证明使用了不可并行加速的算法,对于一个计算量证明,机器人没法用 GPU 加速运算,也没法充分利用多核 CPU 加速运算,只能老老实实地在单核上进行计算。

当然,机器人可以在多核心上并发生成多个计算量证明,然后并发进行请求,这是 wCaptcha 的弱点之一,不过可以在工程上用一些额外的机制补强这个弱点。例如,对于登录接口来说,我们可以将计算量证明与账号关联起来,一旦计算量证明被提交,此前签发的所有关于这个账号的计算量证明问题就被废弃,这样就可以保证机器人没法并发对一个账号进行破解了。

更进一步地,我们还可以根据机器人的 IP 地址或其他可以识别机器人的信息,对计算量证明的难度进行变更:如果一个 IP 地址在同一时间内提交了太多次计算量证明,我们可以临时提高这个 IP 地址需要提供的计算量证明的难度。这样就在用户体验和安全性之间得到了一个比较好的平衡。

上面说过,wCaptcha 只能限制需要大规模发送请求的机器人,对于那些不需要频繁发送请求的机器人,wCaptcha 是无能为力的。不过在实践中,一般都是是在需要大量发送请求的时候才会使用到自动程序。

# 一些常见的问题

# 关于计算量证明

wCaptcha 的计算量证明的核心算法就是连续平方取模,用伪代码来说话就是:

1p = /* 一个服务端预先生成的大整数 */
2n = random()
3for i = 0 to 2^22
4    n = (n * n) mod p
5do
6// 最终的 n 就是计算量证明

可以看到这个算法是没有办法被并行加速的。而对于服务端来说,它知道大整数 p 的因数分解,于是可以通过一种快速算法迅速计算出 n,这就保证了服务端可以以很快的速度验证客户端花了很长时间才计算出的结果是否正确。

# 关于浏览器和原生程序的速度差异

目前 wCaptcha 使用 WASM 进行算计,根据实测,速度大约是原生程序的 $ \frac{1}{6} $ 。客户端花费 6 秒 CPU 时间计算出的结果,原生程序只需要 1 秒就能得结果。

虽然说客户端速度比较慢,但我们可以在用户开始填写表单的时候就开始生成计算量证明,等用户填好表单,计算量证明也生成好了。而 1 秒的时间对于原生程序来说虽然比客户端快了很多,但是这仍是无法忽略的 CPU 开销,如果要发送大量请求的话,还是要掂量掂量。

# 最后给网址

wCaptcha 官网是 https://wcaptcha.pingflash.com

上面有演示页面,也有详细的说明,代码也开源到了 github 上。