极光验证码,使用工作量证明(PoW)算法解决人机识别问题.传统的验证码是使用图片/音频等人类可以识别的因素来区分机器与人类.使用工作量证明的意义在于,访问站点时,需要花时间来计算一些有难度的数据,再提交到服务器进行验证.在人类访问站点时,操作频率往往不会太高,这些数据比较快就能算出来.当机器人爬取站点数据或者做帐号密码爆破时,需要进行多次数据计算,导致机器人的CPU 占用变高,消耗其硬件资源
极光验证码无需用户输入任何数据,只需要点击图片即可
Demo 程序路径是server.py ,运行之后访问地址http://127.0.0.1/.
,在浏览器会看到这个界面
点击图片验证码之后,浏览器会创建线程来执行大量的hash 计算
计算结果完毕之后,提示验证通过
Gif 演示动画
目前Demo 版只支持Python Torando ,以后可能会移植到PHP 版本
极光验证码的文件如下:
pow.py 工作量证明生成与验证逻辑
captcha.py 验证码Tick 查询逻辑
验证码的使用逻辑封装在captcha.py
里,只需要import captcha
即可使用.首先在tornado 里注册验证码需要用到的handle
class get_captcha_handle(tornado.web.RequestHandler) 获取验证码
class valid_captcha_handle(tornado.web.RequestHandler) 校验验证码
示例代码:
handler = [
('/get_captcha',captcha.get_captcha_handle) , # 极光验证码CGI
('/valid_captcha',captcha.valid_captcha_handle) ,
('/captcha/(.*)',tornado.web.StaticFileHandler,{'path':'captcha'}) , # 极光验证码静态文件
('/captcha_picture/(.*)',tornado.web.StaticFileHandler,{'path':'captcha_picture'}) ,
]
http_server = tornado.web.Application(handlers = handler)
至此,后端已经完成验证码模块的导入,我们还需要做的最后一件事是对用户上传的Tick2 进行验证,验证的接口在captcha.py
import captcha # 导入极光验证码
# 省略多余代码
captcha.captcha.check_tick(tick_id) # 只需要传递tick2 到check_tick() 函数即可得到验证码校验结果
check_tick()
将会返回三个值:
class tick_result :
tick_state_success = 0 # 验证成功
tick_state_error = 1 # 验证不正确
tick_state_expire = 2 # 验证码过时
示例代码:
class login_handle(tornado.web.RequestHandler) : # server.py 的代码
def post(self) :
tick_id = self.get_argument('tick') # 获取浏览器提交上来的Tick2
valid_state = captcha.captcha.check_tick(tick_id) # 验证Tick2 的结果
if captcha.tick_result.tick_state_success == valid_state : # 验证码通过
guest_code = self.get_argument('guest_code')
if '514230' == guest_code :
result = 'Pass Success'
else :
result = 'Pass Error'
elif captcha.tick_result.tick_state_expire == valid_state : # 验证码过期
result = 'Captcha Expire ..'
else :
result = 'Captcha Error ..'
self.write(json.dumps({
'status' : result
}))
导入验证码模块和验证码UI 还在设计中,后面再更新
当验证码计算完成并且获取到Tick2 时,会把Tick2 保存在全局变量pass_tick
中,在接下来和后端的校验中直接把Tick2 上传到服务器即可(Tick2 使用完毕之后会立即释放)
示例代码:
function submit() {
if (undefined == window.pass_tick) { // 判断pass_tick 是否计算完成
alert('Please Click CAPTCHA ..');
return 'No Click Captcha';
}
guest_code = document.getElementById('guest_code');
post_data = {
'guest_code' : guest_code.value ,
'tick' : window.pass_tick // 直接读取全局变量pass_tick 获取Tick2
}
check_state = request_post('/login',post_data);
alert(check_state['status']);
return check_state;
}
下图是极光验证码的工作原理,注意Tick1 和Tick2 的区别
1.首先,浏览器加载到验证码,向服务器请求数据/get_captcha
2.接下来,服务器随机生成工作量计算数据和Tick1,Tick1 的意义在于给工作任务定义一个唯一ID
3.浏览器获取到工作量计算数据之后,进行大量的hash 计算,最后返回工作量计算到服务器验证计算工作/valid_captha
4.然后,服务器对浏览器的工作量计算进行验证,并分配Tick2 ,Tick2 用于保存验证的结果
5.最后,浏览器把需要验证/获取的数据加上Tick2 上传到服务器,让服务器对验证码和数据进行验证
工作量证明是指系统为达到某目标而设置的工作度量方法,需要由工作者和验证者两方共同完成.
1.工作者需要完成的工作必须有一定的量,这个量由验证者给出
2.验证者可以迅速的检验工作量是否达标,注意这里的检验完成过程必须简单
示例代码:
import hashlib
def sha256(data) :
return hashlib.sha256(data).digest()
def valid(data = 'test',nonce = '000') :
start_time = time.time()
data = sha256(data)
calcute = 0
while not data.startswith(nonce) :
data = sha256(data)
calcute += 1
end_time = time.time()
return end_time - start_time,calcute
这段代码的意义在于,不断地对data 进行sha256 运算,一直寻找以nonce 开头的hash 运算结果,运行这段代码,结果如下:
结果显示,以'test' 为初始字符串不断进行sha256 运算,要找到'000' 未开头的hash 字符串需要用时16 秒,总计执行12118482 次计算过程
那么服务器需要怎么样验证这个结果呢,方法很简单,获取最后hash 出来的结果的前一次hash 数据即可
import hashlib
def sha256(data) :
return hashlib.sha256(data).digest()
def calculate(data = 'test',nonce = '000') :
start_time = time.time()
last_hash = data
data = sha256(data)
calcute = 0
while not data.startswith(nonce) :
last_hash = data
data = sha256(data)
calcute += 1
end_time = time.time()
return end_time - start_time,calcute,last_hash
def valid_hash(hash_data,nonce = '000') :
if sha256(hash_data).startswith(nonce) :
return True
return False
校验结果如下:
屏幕面前的你已经知道,这样是存在问题的,当黑客找到一条符合判断的hash 之后,valid_hash()
返回来的结果一定是True ,也就绕过了hash 计算,为了计算的结果足够随机,我使用了四要素
string 字段 初始计算数据
bit_flag 字段 目的hash 结果中要出现的标识
bit_offset 字段 指定bit_flag 在hash 结果偏移bit_offset 个字节位置中出现
hash_loop 字段 提交目的hash 结果的前hash_loop 次hash 结果
计算逻辑如下:
服务器能够很快地对客户端上传过来的结果进行校验
def valid_pow(pow_list) : # valid_pow() 的代码在pow.py 里
try :
for pow_index in pow_list : # 注意这里有多个工作量证明的计算任务
result_hash = pow_index['data'] # 读取客户端计算结果
bit_flag = pow_index['bit_flag']
bit_offset = pow_index['bit_offset']
hash_loop = pow_index['hash_loop']
for hash_index in range(hash_loop) : # 对data 进行hash_loop 次sha256 运算
result_hash = sha256(result_hash)
if not bit_flag == result_hash[ bit_offset : bit_offset + random_bit_flag_length ] : # 判断hash 检验位
return False
return True
except :
pass
return False
既然没人看,那就偷个懒.略略略...