点击上方蓝字[协议分析与还原]关注我们


分析今日头条内小视频和西瓜视频分享后浏览器打开所用的signature签名算法。

上月写的一篇关于使用微信的wxid加好友的文章,竟然无意间碰到了一个微信搜索黑洞,成就了本号有史以来搜索量、阅读量、关注度最大的文章,看样子纯粹的干货受众面有限,还是关注度高的信息才能带来更大的收获。

为更好地满足大家的需求,可以发送“wxid”获取微信使用wxid加好友的具体方法截图,毕竟我没法一条条请求都回复,也没法手把手地教,各位解决问题还是以自助为主。

下面是这篇文章,有需要的朋友可以点进去学习:

微信wxid搜索用户不存在,怎么加好友?

今天这篇文章,对今日头条的签名算法进行分析,相当干货,里面的知识在今日头条相关的技术应用中使用非常广泛,希望大家能够学习愉快。

今日头条作为一大当红炸子鸡,很多人都盯着,当然,爬取头条里各种类型的数据必不可少,网上很多分析头条signature签名算法的文章,但都不尽兴,有些问题没有明确,导致实践起来却有些问题,这里将给出signature签名更有价值的说明。

另外,不得不说,js是世界上最垃圾的语言,希望早日退出历史的舞台。

01

signature是什么?

在头条里,对一些关键节点的数据,服务器会进行校验,校验的一部分,就是在客户端对数据进行签名,在服务器对数据进行合法性检查,这样,就能够过滤掉很大一部分不合法的请求。

例如,在访问一个头条分享的视频过程中,浏览器会自动请求下面这个URL:

https://m.365yg.com/i6714101379172027656/info/?_signature=lyZkrxARynO8Pdz-QfDCYJcmZL&i=6714101379172027656

这个里面,_signature在它前后的报文中是没有的,是头条服务器下发下来的js根据一些参数计算出来的,也就是签名。

这个_signature如果错误,服务器就会发现,就不会返回我们真正需要的报文。

上面的这个URL,是用来请求视频的一些关键信息的,如果_signature错误,则会返回一个头条自己的默认视频。

因此,signature就是一个由客户端根据头条下发的js算法生成的校验值,即签名,服务器将会对它进行校验,并根据校验情况作出不同的反馈。

02

签名算法

知道了头条的signature签名是怎么回事,大家一定会想到,既然signature值是在客户端由js生成的,而js我们能够轻松拿到,并且js代码是没法真正加密的,那这个签名值不就很容易算出来吗?

我一开始也是这么想的,当看到了生成signature签名的js算法的时候,我气馁了,这到底是什么玩意?!经过了混淆,里面到处是乱码,没有格式,一堆莫名其妙的字符串。

那就正儿八经地一点点的分析吧。

先说下,这个签名算法很早前就有人将它逆向出来了,网上搜“头条signature”就能找到一大堆,经过确认,在头条小视频和西瓜视频中使用的就是这个算法。

这里介绍下符合头条小视频和西瓜视频的分析过程,以便更好的使用。

使用fiddler抓包,很容易就能抓到头条的各类js文件,其中,签名算法在xigua_video.38p5Q8hF.js这个文件中,下面是对文件格式化后的签名部分:

看着很崩溃吧,里面一堆乱码,这是js所谓的特性,除了制造不必要的阻碍,没有任何作用,我们要有信心,只要有能运行js的环境,js必然是裸奔,最终都将还原成浏览器可以识别的代码,事实也确实是这样。

一步步来把它解开,先将前面那部分来尝试解开,下面是代码,直接就输出结果了:

var x =function(l){return'e(e,a,r){(b[e]||(b[e]=t("x,y","x "+e+" y")(r,a)}a(e,a,r){(k[r]||(k[r]=t("x,y","new x[y]("+Array(r+1).join(",x[y]")(1)+")")(e,a)}r(e,a,r){n,t,s={},b=s.d=r?r.d+1:0;for(s["$"+b]=s,t=0;t<b;t)s[n="$"+t]=r[n];for(t=0,b=s=a;t<b;t)s[t]=a[t];c(e,0,s)}c(t,b,k){u(e){v[x]=e}f{g=,ting(bg)}l{try{y=c(t,b,k)}catch(e){h=e,y=l}}for(h,y,d,g,v=[],x=0;;)switch(g=){case 1:u(!)4:f5:u((e){a=0,r=e;{c=a<r;c&&u(e[a]),c}}(6:y=,u((y8:if(g=,lg,g=,y===c)b+=g;else if(y!==l)y9:c10:u(s(11:y=,u(+y)12:for(y=f,d=[],g=0;g<y;g)d[g]=y.charCodeAt(g)^g+y;u(String.fromCharCode.apply(null,d13:y=,h=delete [y]14:59:u((g=)?(y=x,v.slice(x-=g,y:[])61:u([])62:g=,k[0]=65599*k[0]+k[1].charCodeAt(g)>>>065:h=,y=,[y]=h66:u(e(t[b],,67:y=,d=,u((g=).x===c?r(g.y,y,k):g.apply(d,y68:u(e((g=t[b])<"<"?(b--,f):g+g,,70:u(!1)71:n72:+f73:u(parseInt(f,3675:if(){bcase 74:g=<<16>>16g76:u(k[])77:y=,u([y])78:g=,u(a(v,x-=g+1,g79:g=,u(k["$"+g])81:h=,[f]=h82:u([f])83:h=,k[]=h84:!085:void 086:u(v[x-1])88:h=,y=,h,y89:u({e{r(e.y,arguments,k)}e.y=f,e.x=c,e})90:null91:h93:h=0:;default:u((g<<16>>16)-16)}}n=this,t=n.Function,s=Object.keys||(e){a={},r=0;for(c in e)a[r]=c;a=r,a},b={},k={};r'.replace(/[-]/g,function(e){return l[15&e.charCodeAt(0)]})}("v[x++]=v[--x]t.charCodeAt(b++)-32function return ))++.substrvar .length(),b+=;break;case ;break}".split(""));
console.log(x);

结果如下:

function e(e,a,r){return (b[e]||(b[e]=t("x,y","return x "+e+" y")))(r,a)}function a(e,a,r){return (k[r]||(k[r]=t("x,y","return new x[y]("+Array(r+1).join(",x[++y]").substr(1)+")")))(e,a)}function r(e,a,r){var n,t,s={},b=s.d=r?r.d+1:0;for(s["$"+b]=s,t=0;t<b;t++)s[n="$"+t]=r[n];for(t=0,b=s.length=a.length;t<b;t++)s[t]=a[t];return c(e,0,s)}function c(t,b,k){function u(e){v[x++]=e}function f(){return g=t.charCodeAt(b++)-32,t.substring(b,b+=g)}function l(){try{y=c(t,b,k)}catch(e){h=e,y=l}}for(var h,y,d,g,v=[],x=0;;)switch(g=t.charCodeAt(b++)-32){case 1:u(!v[--x]);break;case 4:v[x++]=f();break;case 5:u(function (e){var a=0,r=e.length;return function (){var c=a<r;return c&&u(e[a++]),c}}(v[--x]));break;case 6:y=v[--x],u(v[--x](y));break;case 8:if(g=t.charCodeAt(b++)-32,l(),b+=g,g=t.charCodeAt(b++)-32,y===c)b+=g;else if(y!==l)return y;break;case 9:v[x++]=c;break;case 10:u(s(v[--x]));break;case 11:y=v[--x],u(v[--x]+y);break;case 12:for(y=f(),d=[],g=0;g<y.length;g++)d[g]=y.charCodeAt(g)^g+y.length;u(String.fromCharCode.apply(null,d));break;case 13:y=v[--x],h=delete v[--x][y];break;case 14:v[x++]=t.charCodeAt(b++)-32;break;case 59:u((g=t.charCodeAt(b++)-32)?(y=x,v.slice(x-=g,y)):[]);break;case 61:u(v[--x][t.charCodeAt(b++)-32]);break;case 62:g=v[--x],k[0]=65599*k[0]+k[1].charCodeAt(g)>>>0;break;case 65:h=v[--x],y=v[--x],v[--x][y]=h;break;case 66:u(e(t[b++],v[--x],v[--x]));break;case 67:y=v[--x],d=v[--x],u((g=v[--x]).x===c?r(g.y,y,k):g.apply(d,y));break;case 68:u(e((g=t[b++])<"<"?(b--,f()):g+g,v[--x],v[--x]));break;case 70:u(!1);break;case 71:v[x++]=n;break;case 72:v[x++]=+f();break;case 73:u(parseInt(f(),36));break;case 75:if(v[--x]){b++;break}case 74:g=t.charCodeAt(b++)-32<<16>>16,b+=g;break;case 76:u(k[t.charCodeAt(b++)-32]);break;case 77:y=v[--x],u(v[--x][y]);break;case 78:g=t.charCodeAt(b++)-32,u(a(v,x-=g+1,g));break;case 79:g=t.charCodeAt(b++)-32,u(k["$"+g]);break;case 81:h=v[--x],v[--x][f()]=h;break;case 82:u(v[--x][f()]);break;case 83:h=v[--x],k[t.charCodeAt(b++)-32]=h;break;case 84:v[x++]=!0;break;case 85:v[x++]=void 0;break;case 86:u(v[x-1]);break;case 88:h=v[--x],y=v[--x],v[x++]=h,v[x++]=y;break;case 89:u(function (){function e(){return r(e.y,arguments,k)}return e.y=f(),e.x=c,e}());break;case 90:v[x++]=null;break;case 91:v[x++]=h;break;case 93:h=v[--x];break;case 0:return v[--x];default:u((g<<16>>16)-16)}}var n=this,t=n.Function,s=Object.keys||function (e){var a={},r=0;for(var c in e)a[r++]=c;return a.length=r,a},b={},k={};return r

到网上找个网站格式化下,会发现和网上公开的被破解出来的头条signature代码的get_as_cp_signature()其中的一部分基本一致。

在get_as_cp_signature这个函数中,我们只需要TAC.sign这一部分,所以as,cp这两个值的生成相关的可以直接删掉,简化代码。

删除生产as,cp这两个值的之后的部分就是用来生成签名的加密算法,而生成加密算法的素材就是紧跟在后面的内容了:

("v[x++]=v[--x]t.charCodeAt(b++)-32function return ))++.substrvar .length(),b+=;break;case ;break}".split("")))()('gr$Daten 袠b/s!l y蛼y墓g,(lfi~ah`{mv,-n|jqewVxp{rvmmx,&effkx[!cs"l".Pq%widthl"@q&heightl"vr*getContextx$"2d[!cs#l#,*;?|u.|uc{uq$fontl#vr(fillTextx$$榫樴笐喔犼步2<[#c}l#2q*shadowBlurl#1q-shadowOffsetXl#$$limeq+shadowColorl#vr#arcx88802[%c}l#vr&strokex[ c}l"v,)}eOmyoZB]mx[ cs!0s$l$Pb<k7l l!r&lengthb%^l$1+s$jl  s#i$1ek1s$gr#tack4)zgr#tac$! +0o![#cj?o ]!l$b%s"o ]!l"l$b*b^0d#>>>s!0s%yA0s"l"l!r&lengthb<k+l"^l"1+s"jl  s&l&z0l!$ +["cs\'(0l#i\'1ps9wxb&s() &{s)/s(gr&Stringr,fromCharCodes)0s*yWl ._b&s o!])l l Jb<k$.aj;l .Tb<k$.gj/l .^b<k&i"-4j!+& s+yPo!]+s!l!l Hd>&l!l Bd>&+l!l <d>&+l!l 6d>&+l!l &+ s,y=o!o!]/q"13o!l q"10o!],l 2d>& s.{s-yMo!o!]0q"13o!]*Ld<l 4d#>>>b|s!o!l q"10o!],l!& s/yIo!o!].q"13o!],o!]*Jd<l 6d#>>>b|&o!]+l &+ s0l-l!&l-l!i\'1z141z4b/@d<l"b|&+l-l(l!b^&+l-l&zl\'g,)gk}ejo{cm,)|yn~Lij~em["cl$b%@d<l&zl\'l $ +["cl$b%b|&+l-l%8d<@b|l!b^&+ q$sign ', [Object.defineProperty(r, "__esModule", {
value: !0})])};

又是乱码,网上公开的get_as_cp_signature() 算法将它转成url编码了:

r(decodeURIComponent("gr%24Daten%20%D0%98b%2Fs!l%20y%CD%92y%C4%B9g%2C(lfi~ah%60%7Bmv%2C-n%7CjqewVxp%7Brvmmx%2C%26eff%7Fkx%5B!cs%22l%22.Pq%25widthl%22%40q%26heightl%22vr*getContextx%24%222d%5B!cs%23l%23%2C*%3B%3F%7Cu.%7Cuc%7Buq%24fontl%23vr(fillTextx%24%24%E9%BE%98%E0%B8%91%E0%B8%A0%EA%B2%BD2%3C%5B%23c%7Dl%232q*shadowBlurl%231q-shadowOffsetXl%23%24%24limeq%2BshadowColorl%23vr%23arcx88802%5B%25c%7Dl%23vr%26strokex%5B%20c%7Dl%22v%2C)%7DeOmyoZB%5Dmx%5B%20cs!0s%24l%24Pb%3Ck7l%20l!r%26lengthb%25%5El%241%2Bs%24j%02l%20%20s%23i%241ek1s%24gr%23tack4)zgr%23tac%24!%20%2B0o!%5B%23cj%3Fo%20%5D!l%24b%25s%22o%20%5D!l%22l%24b*b%5E0d%23%3E%3E%3Es!0s%25yA0s%22l%22l!r%26lengthb%3Ck%2Bl%22%5El%221%2Bs%22j%05l%20%20s%26l%26z0l!%24%20%2B%5B%22cs'(0l%23i'1ps9wxb%26s()%20%26%7Bs)%2Fs(gr%26Stringr%2CfromCharCodes)0s*yWl%20._b%26s%20o!%5D)l%20l%20Jb%3Ck%24.aj%3Bl%20.Tb%3Ck%24.gj%2Fl%20.%5Eb%3Ck%26i%22-4j!%1F%2B%26%20s%2ByPo!%5D%2Bs!l!l%20Hd%3E%26l!l%20Bd%3E%26%2Bl!l%20%3Cd%3E%26%2Bl!l%206d%3E%26%2Bl!l%20%26%2B%20s%2Cy%3Do!o!%5D%2Fq%2213o!l%20q%2210o!%5D%2Cl%202d%3E%26%20s.%7Bs-yMo!o!%5D0q%2213o!%5D*Ld%3Cl%204d%23%3E%3E%3Eb%7Cs!o!l%20q%2210o!%5D%2Cl!%26%20s%2FyIo!o!%5D.q%2213o!%5D%2Co!%5D*Jd%3Cl%206d%23%3E%3E%3Eb%7C%26o!%5D%2Bl%20%26%2B%20s0l-l!%26l-l!i'1z141z4b%2F%40d%3Cl%22b%7C%26%2Bl-l(l!b%5E%26%2Bl-l%26zl'g%2C)gk%7Dejo%7B%7Fcm%2C)%7Cyn~Lij~em%5B%22cl%24b%25%40d%3Cl%26zl'l%20%24%20%2B%5B%22cl%24b%25b%7C%26%2Bl-l%258d%3C%40b%7Cl!b%5E%26%2B%20q%24sign%20"), [TAC = {}]);

简单点说,签名算法一直保持不变,名字就是TAC.sign。

再继续找,找到签名,知道被签名值即入参是前面出现的url中的i,即视频id值:

var _signature = (0, TAC.sign)("6714101379172027656");

但是,遗憾的是,这样生成的签名signature值,在构造请求时,却无法有效使用,访问本文的朋友估计都是出现了这一问题,前面的是干货,但没有新意,后面的才是独家的,可以解决构造请求时,signature值却无效的问题。

signature无效,是因为签名算法TAC.sign与很多个变量有关。

03

签名算法解析

整个签名算法,就是一个给各种数据算sdbmhash哈希的过程。算哈希的内容不同,当然结果不同。

首先,毫无疑问,signature值与入参有关,在访问头条小视频和西瓜视频的请求中,这个入参是url中的i值,即视频id值。

其次,在访问头条小视频和西瓜视频的请求中,signature签名与一个tac值有关,在发起前文中那个url请求的页面中存在:

<script data-from="toutiao">tac='i)69gg22apbs!i$13zns"0,<8~z|\x7f@QGNCJF[\\^D\\KFYSk~^WSZhg,(lfi~ah`{md"inb|1d<,%Dscafgd"in,8[xtm}nLzNEGQMKAdGG^NTY\x1ckgd"inb<b|1d<g,&TboLr{m,(\x02)!jx-2n&vr$testxg,%@tug{mn ,%vrfkbm[!cb|'</script>

当然也是乱码,不用管它,直接用就行了。

再次,signature签名与还与浏览器userAgent有关,即发起请求的浏览器UA,当然,伪造手机请求得用手机上能出现的UA,这个在网上公开的那个破解出的代码头部也有出现:

navigator1 = {
    userAgent: "Mozilla/5.0 (Linux; Android 8.8.2; xxxxx Build/g) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.109 Mobile Safari/537.36"
}, window = this, window.navigator = navigator1;

最后,signature签名还与第二段乱码中的一个乱码字造出的图像值有关,根据调试趟过的坑,在PC和手机上大概率这个图像是不一样的,在PC上,乱码生成的图片是这样子:

这个玩意的sdbmhash哈希,和手机上产生的报文里signature值中的哈希是不等的,我只好用蛮办法,爆破出我用来调试的手机上的哈希是:723236945,而PC上的算法里这个图像产生的哈希是:3311753357。

控制好这四个内容,就能够产生正确的signature值了,最终生成的一个26字节的signature值,根据调试过程,可以按下面分成5个部分:

j5qI5 xAY0r xK.9Bq 6f.DU 4-aiP
5a   |5b   |6c    |5d   |5e
6c与构造的乱码字图像有关
5d与浏览器UA有关
tac与所有的内容有关
视频id值与5d有关 

04

福利

前面提到,要控制四个值,才能生成可用的signature值,其中,视频id是入参,tac值,可以直接在代码中赋值,这两个都好控制,但UA值,确是js从当前运行环境自动获取的,乱码字图像,也是通过一些js系统函数生成的,没法控制产生过程,不容易替换。

这里提供替换代码,首先是UA的替换,在get_as_cp_signature的case 77中:

  case 77:
y = v[--x];
    p00 = v[--x];
if("navigator"==y)
{
p = navigator1;
}
    else
{
        p = p00[y];
    }
u(p);
// y = v[--x],
// u(v[--x][y]);
break;

其次是乱码字图像哈希值的替换,在get_as_cp_signature的case 62中:

case 62:
g = v[--x],
k[0] = 65599 * k[0] + k[1].charCodeAt(g) >>> 0;
if(k[0]==3311753357)
      k[0]=723236945;
   break;

这都是一步步调出来的血汗代码呀。

祝大家工作愉快。

要记得多多关注我,你给我动力,我给你想要的干货。

另外,我把赞赏打开了,嘻嘻,不习惯的可以忽略,重要的是,如果文章有价值,点击右下方“在看”,与朋友们一起分享吧↘。


头条小视频和西瓜视频signature签名算法的更多相关文章

  1. 西瓜视频蓝光1080P下载方法

    西瓜视频的蓝光画质只能在APP上看,如何获取1080P画质的地址呢? 1.先安装 WinPcap 2.然后安装夜神安卓模拟器NOX 3.NOX模拟器里安装西瓜视频的最新APP,旧版本APP只提供超清模 ...

  2. 爬虫技巧-西瓜视频MP4地址获取

    记录一下西瓜视频MP4地址的获取步骤 目标: 指定西瓜视频地址,如 https://www.ixigua.com/a6562763969642103303/#mid=6602323830,获取其视频M ...

  3. 【小程序开放激励视频】--wepy小程序添加激励视频

    小程序开放激励视频是对小程序开发者一个福音,小程序开发者可以完成一些变现,以增加收入! 本文章针对已经有开发经验或者正在进行小程序开发的同学~ 官方文档:激励视频广告 定义页面变量,用于创建视频实例 ...

  4. 一款好用的视频转换gif的小软件——抠抠视频秀

           在平常生活中,我们拍下来精彩的视频想要转换为gif动画,或是想要录制网页上的视频.电脑上的鼠标操作等等,大家可以使用以下这款很好用的视频转换gif的小软件——抠抠视频秀,这个软件操作简单 ...

  5. 推荐一款移动端小视频App玲珑视频

    推荐一款移动端小视频App玲珑视频 一 应用描述 玲珑小视频,边看边聊![海量视频,刷个不停,还能找妹子语音聊天哦][随手拍一拍,记录美好生活,还能拿金币哦][看视频领金币.登录领金币.拍视频领金币. ...

  6. OpenCV录制视频,播放视频

    一.录制视频 获取摄像头操作 camObj = cv2.VideoCapture(0) :0为默认计算机默认摄像头,多个摄像头依次后面1.2.3 检查是否成功初始化: camObj.isOpen() ...

  7. iOS - 直播流程,视频推流,视频拉流,简介,SMTP、RTMP、HLS、 PLPlayerKit

    收藏笔记 1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示1.数据采集:摄像机及拾音器收集视频及音频数据,此时得到的为原始数据涉及技术或协议:摄像机: ...

  8. Android开发 多媒体提取器MediaExtractor详解_将一个视频文件分离视频与音频

    前言 此篇博客讲解MediaExtractor将一个视频文件分离视频与音频,如果你对MediaExtractor还没有一个笼统的概念建议先了解我的另一篇入门博客:https://www.cnblogs ...

  9. 【转】直播流程,视频推流,视频拉流,简介,SMTP、RTMP、HLS、 PLPlayerKit

    原:https://www.cnblogs.com/baitongtong/p/11248966.html 1 .音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放 ...

随机推荐

  1. 一文搞懂V8引擎的垃圾回收

    引言 作为目前最流行的JavaScript引擎,V8引擎从出现的那一刻起便广泛受到人们的关注,我们知道,JavaScript可以高效地运行在浏览器和Nodejs这两大宿主环境中,也是因为背后有强大的V ...

  2. Mybatis_resultMap的关联方式实现多表查询(一对多)

    a)在 ClazzMapper.xml 中定义多表连接查询 SQL 语句, 一次性查到需要的所有数据, 包括对应学生的信息. b)通过<resultMap>定义映射关系, 并通过<c ...

  3. BZOJ1500 [NOI2005]维修数列(Splay tree)

    [Submit][Status][Discuss] Description 请写一个程序,要求维护一个数列,支持以下 6 种操作: 请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格 Inp ...

  4. 搭建Squid3 密码账号IP代理

    上文中,说明了 Squid3 IP Proxy 隐藏原IP,这里就搭建Squid 3密码账号IP代理进行整理,涉及环境 Ubuntu 18.04. Step 1: htpasswd 和 htdiges ...

  5. 从头学pytorch(二) 自动求梯度

    PyTorch提供的autograd包能够根据输⼊和前向传播过程⾃动构建计算图,并执⾏反向传播. Tensor Tensor的几个重要属性或方法 .requires_grad 设为true的话,ten ...

  6. ARTS-S C语言多线程传参数

    #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h& ...

  7. Java多态之动态绑定

    目录 Java多态之动态绑定 引用变量的类型 编译时类型 运行时类型 方法绑定 静态绑定 动态绑定 方法表 Java多态之动态绑定 上篇回顾:多态是面向对象程序设计非常重要的特性,它让程序拥有 更好的 ...

  8. postman设置全局变量

    //处理token var jsn = JSON.parse(responseBody) console.log(jsn.access_token) //把access_token设置到全局变量中 p ...

  9. MySQL使用可重复读作为默认隔离级别的原因

    一般的DBMS系统,默认都会使用读提交(Read-Comitted,RC)作为默认隔离级别,如Oracle.SQL Server等,而MySQL却使用可重复读(Read-Repeatable,RR). ...

  10. python学习-os引入

    # 引入import os # 路径处理 -- 外部资源-os # 获取当前的工作路径workspace = os.getcwd() # os模块下的getcwd函数print(workspace) ...