请求数据通过URL加入sign验证加密与解密

1.通过Sign加密的请求URL案例

不少网站做数据反爬虫会做一系列的的措施,就包括这里要说的url加密,例如如下url是请求b站游戏id为109905的游戏的评分数据 (无意冒犯,仅首页随机选取做学习案例使用):

https://line1-h5-pc-api.biligame.com/game/comment/summary?game_base_id=109905&ts=1679988261931&request_id=YKceEELUnX5m4aELQACuqX2mG48wv13B&appkey=h9Ejat5tFh81cq8V&sign=00d3c6791f798be86bac45576b31dc32

大家能看出, 这个请求包含了5个参数:

game_base_id: 109905
ts: 1679988261931
request_id: YKceEELUnX5m4aELQACuqX2mG48wv13B
appkey: h9Ejat5tFh81cq8V
sign: 00d3c6791f798be86bac45576b31dc32

通过此请求可以得到正常的返回数据:

{“code”:0,“message”:“成功”,“request_id”:“9dbfc9cac59045709dcda62e32cac670”,“ts”:1679988274628,“data”:{“grade”:8.3,“comment_number”:1709,“valid_comment_number”:1704,“star_number_list”:[246,59,69,41,951],“state”:3}}

回头分析那5个参数,明显对于用户和实际申请数据来说, 只需要game_baseid这一个参数就足够了.
ts明显是时间戳,而request_id翻译过来应该是随机生成的请求ID.
根据经验, 参数sign放请求参数里就大多是加密了. 那么我们假设其他4个数据都是作为加密使用的.
为了验证我们的想法, 我们可以任意修改这5个参数中的一个.
发现无论修改五个中的哪一个, 都会得到错误的返回:

{“code”:-1203,“message”:“invalid request”}

说明五个参数都参与了加密或者验证.

当其他人想通过复制这个URL, 然后仅修改game_base_id参数来获取各游戏的数据, 就都会遇到 invalid request的错误返回.

2.解密, 考察sign加密方式

这是个不错的防爬虫方案, 那么这个sign具体是如何生成的呢. 我们可以去测试更多的其他请求,来对比所有请求的参数构造.
如appid分别为107681和141的请求的参数:

game_base_id: 107681
ts: 1679989802458
request_id: WjSH6StUYwo0LcFi5yhvfoopzWXKdNTU
appkey: h9Ejat5tFh81cq8V
sign: 528c7740a4e62d01e6f208db4f1409ac

game_base_id: 141
ts: 1679989855423
request_id: vYUoWMZTDHAx6wnFYh6akUXzCmcNd060
appkey: h9Ejat5tFh81cq8V
sign: 1687d53da12c13fb8c3a2d207d434fc4

通过比较多个请求,我们发现

  1. sign都是32位, 而常用的md5加密也有32位
  2. appkey都没变
  3. request_id通常都是随机的,只是参与加密验证,而不会直接验证真伪

之后的解密稍微走了些弯路: 暴力尝试. 这个在踩坑环节再说. 下面继续说正确的解密姿势

既然目前最大的可能是通过sign加密,那么我们就开始下一步,找源码.
我这里是chrome浏览器, 按F12进入调试模式, F5刷新请求数据的页面. Ctrl+Shift+F打开全局搜索, 搜 sign, 会搜到很多, 快速过滤掉不对的. 就找到了这一段代码:
在这里插入图片描述

	i[n(137)] = "h9Ejat5tFh81cq8V",
	n(157));
	return i.sign = h(""[n(130)](d[n(122)](i, {
	    sort: function(t, e) {
	        return t[n(152)](e)
	    }
	}))[n(130)](e))

注意到这里甚至有我们的appkey, 所以基本可以确定这里就是我们要找到JS代码. 这里sign应该就是我们想找的算法, 但是这段JS被混淆处理了.
这里又走了些弯路–反混淆化, 也在后面踩坑环节说, 先继续正确的解密方法
我们在这里打断点,刷新页面
在这里插入图片描述首先看到这最外围的h()函数还是一个未知函数,我们先不管. 然后复制函数里所有东西,到控制台输出
在这里插入图片描述

一下就明朗了, 里面就是被加密的字符串. 当然我们可以继续细致的看各个部分的含义,比如出现了2次的n(130)函数是concat() 用concat连接的这段函数,得到的是拼起来的parameters. 包含了sign以外的全部4个参数. 拼接格式也得到了, 用’&'拼接, 而最后还拼接了一个字符串, 可以知道这个字符串是变量e.
这个e是不属于参数的, 我们称呼它为secret. 至于这个值具体如何取的. 我们先验证这个值是否固定, 如果不固定, 那么就要继续逆推JS.
我们再去打开其他appid的网站.重复以上调试方式.发现这个e值固定不变.
另外我们注意到代码里有个sort关键字. 因此sign的计算方式已经呼之欲出了:

将请求中所有明参数按key的字母排序, 以key=value格式化, 再用&拼接起来, 最后再拼接上secret得到原始字符, 对此字符进行md5加密, 得到最后的sign

然后我们来验证我们的想法. 下面是根据此猜想写的一个python自动生成带sign加密参数的URL的代码

def taskMd5():
    import requests
    url = getURL(107681)
    res = requests.get(url)
    if res.status_code == 200:
        print(res.text)

def getURL(gameid):
    appkey = 'h9Ejat5tFh81cq8V'
    secret = #上文提到的e值,这里按网站要求不暴露实际值#
    params = {'appkey': appkey,
             'game_base_id': gameid,
             'request_id': 'cpdThHlVU7jZMfFzn33tBy7AvKMypvP5',
             'ts': math.ceil(time.time()*1000)}
    str_params = []
    for k in params.keys():
        str_params.append(f'{k}={params[k]}')
    org = '&'.join(str_params) + secret
    #print(org)
    sign = getMD5(org)
    #print(sign)
    params['sign'] = sign
    str_params.append(f'sign={sign}')
    url = 'https://line1-h5-pc-api.biligame.com/game/comment/summary?' + '&'.join(str_params)
    #print(url)
    return url

任意测试一个实际存在的gameid, 返回成功

{“code”:0,“message”:“成功”,“request_id”:“c0bdf44236bf428ebc968813d8aad1ea”,“ts”:1679993440018,“data”:{“grade”:8.7,“comment_number”:18167,“valid_comment_number”:17708,“star_number_list”:[1412,394,744,1158,9619],“state”:3}}

验证成功!

至此我们通过此案例介绍了通过sign加密请求的常用方式, 即:
将参与加密的参数(不一定是所有参数), 按一定方式排序, 一定格式拼接, 再加上一个秘xx钥组合成原始字符串, 然后使用MD5或者SHA等加密, 得到sign, 在响应时进行验证

3.踩坑

最后说下坑

  1. 暴力反解MD5误区.
    本以为这个sign加密可能appkey就是秘oo钥. sign可以只通过这4个参数计算, 就用了暴力反解, 尝试各种格式拼接计算出加密结果与实际sign值比较:
def md5_crack():
    format_param = ['{0}={1}', '{1}']
    str_splits = [ '|', '&', '']
    key_format = ['appkey={}', "{}"]
    appkey = 'h9Ejat5tFh81cq8V'
    param = {'game_base_id': '109905', 'request_id': 'ApHV8L2fNmxD5d2dd2mIPsuRo8YJvYhI', 'ts': '1679884518004'}
    sign = "cedaac200fd97738677b4beb3a2c50bd"
    
    for f in format_param:
        for kf in key_format:
            for sp in str_splits:
                str_params = []
                for key in param.keys():
                    concat = f.format(key, param[key])
                    str_params.append(concat)
                alls = [kf.format(appkey)] + str_params
                org = sp.join(alls)
                print(org)
                print(sign==getMD5(org), getMD5(org))
                print()
                alls = str_params + [kf.format(appkey)]
                org = sp.join(alls)
                print(org)
                print(sign==getMD5(org), getMD5(org))
                print()
  1. 反混淆误区. 发现加密代码被混淆化处理之后, 做反混淆的处理, 发现该代码混淆后足足有8W行, 很难完成反混淆处理.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值