1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 网易云api如何调用_分析网易云音乐API的经历

网易云api如何调用_分析网易云音乐API的经历

时间:2020-04-06 10:12:34

相关推荐

网易云api如何调用_分析网易云音乐API的经历

在做qq机器人的时候想加入点歌功能,虽然自带一个点歌插件,但是只能分享QQ音乐,所以我想着自己写一个网易云音乐的。

首先打开网易云音乐的搜索界面,F12打开开发者工具并切换到Network。

随便搜索什么,我们可以看到好几个请求,其中一个是按下搜索按钮的,一个的是在输入框输入的时候请求搜索建议的,还有的不知道什么作用。

这个是真正搜索时候的请求

我们可以看到csrf_token参数甚至都是空的(实际上应该是一个32位的数字字母组合)。

然后Post出去的数据有两个参数params和encSecKey,很显然都是加密过的,其中根据其特征,可以看出来params应该是通过AES加密的(最后有个“=”)。

为了知道加密算法,我们必须要把加密的代码找出来,F12工具选到Sources,在js代码中搜索和这两个参数,在core_xxxxxx的文件里搜到了3个适配项:

这里把两个参数都找到了

如果代码很乱,可以按左下角的{}按钮整理代码。

从代码里面可以知道window.asrsea方法是真正生成了这两个参数的方法,先设个断点(左边行数点击一下)。

随便搜索一下,让它在这里断下。

注意由于所有请求都是通过这个函数加密的,我们要分清楚当前断点是在请求什么,可以通过图中Z5e变量知道请求的url。

url=/weapi/search/suggest/web,当前在请求搜索建议项,并非我们要的

在正确的请求断下,然后在控制台获取各个参数的值

加密函数的参数

我们可以相信,至少第二个和第四个参数应该是固定的(因为其函数参数为常量),第一个参数为真正请求的参数,其中s项对应搜索关键字,csrf_token即为前面的csrf_token。多次改变搜索关键字尝试,可以得出除第一个外其它参数均固定不变。

F10,F11进入加密函数内部:

加密函数内部(d)

加密函数看起来不复杂,首先利用a函数生成一个随机数i,然后通过不同参数(g,i)对d连续加密两次,最后把i加密一次。

这里面有两个加密算法,b很显然就是AES算法,如果想在python里面直接调用AES库,需要安装pycrypto库,安装过程可能会有曲折。

这个AES算法在python下很容易实现:

from

AES算法要求待加密文本位数是16的倍数。和js不同的是,js的加密算法会自动对原文本进行填充,但是python不会,所以我们需要手动对文本进行填充。

from Crypto.Cipher import AESimport base64def encypt_bytes(search_params, key, vi):pad = chr(16 - len(search_params)%16)first = Truewhile (len(search_params) % 16 !=0) or first:search_params += pad.encode('utf-8')first = Falsecipher = AES.new(key, AES.MODE_CBC, vi)return base64.encodebytes(cipher.encrypt(search_params)).replace(b'n', b'').decode('utf-8')

需要注意的是,填充的字符由待填充字符个数决定。若刚好为16的倍数,也需要再填16位。

接下来我们看第二个加密算法,从字面上看是RSA算法。

如果对js不熟悉(比如我),可能会有点理解困难。

setMaxDigits(131)意为设置大数的最大位数(可以忽略,反正python的整型不会溢出)

RSAKeyPair设置一个RSA密钥对,单步调试进去:

RSAKeyPair

biFromHex函数从一个16进制数(的字符串)生成一个大数,由于参数是完全固定的值,我们可以得知chunkSize的值应该是不变的,为126。

而this.e是由16进制数10001产生来的,其值应该为65537。

接下来进入encryptedString函数内部:

encryptedString

代码有点复杂,我们一步步分析。

for

这里的代码很简单,把待加密文本转化为一个数字数组。

for (; 0 != c.length % a.chunkSize; )c[e++] = 0;

当c的长度不是chunkSize(126)的倍数的时候,自动补0。

for (f = c.length,g = "",e = 0; f > e; e += a.chunkSize) {for (j = new BigInt,h = 0,i = e; i < e + a.chunkSize; ++h)j.digits[h] = c[i++],j.digits[h] += c[i++] << 8;k = a.barrett.powMod(j, a.e),l = 16 == a.radix ? biToHex(k) : biToString(k, a.radix),g += l + " "}

先看for循环的条件句,发现其实际上是在分块加密。

但是我们要加密的只是一个16位的随机字符串,完全不会超过126位。

而且,从上面的分析可以看出a.radix一定是16。

所以,可以简化如下:

for (j = new BigInt,h = 0,i = 0; i < a.chunkSize; ++h)j.digits[h] = c[i++],j.digits[h] += c[i++] << 8;k = a.barrett.powMod(j, a.e),g = biToHex(k)

for循环内,j.digits的每一个元素先后在低位高位填充两个数字,假如c=[1,2,3,4],则j.digits为[513, 1027],j表示的数为

为什么是这样的,因为j.digits每个元素实际上可以看做一个4位的16进制数,那我们就可以把j看做一个16^4进制即65536进制数。而每两个c元素填充成为一位。

如果把67,305,985变换成16进制呢?

>>>

实际上就是把01,02,03,04倒过来写!这样就十分好理解了,就如同4个2进制数可以填充成一个16进制数一样,这里的4个16进制数填充成了j(65536进制数),不过是倒着填充的,而且由于每两个字符隔了8bit(一位16进制数只占4bit),所以中间有0,如果c中的数是2位的16进制数,中间就不会有0了。

那么如何在python下面实现呢,实际上python有一个简单的库可以一步搞定。

import binasciidef encryptedString(b, m, e): #m,e都是16进制数b=int(binascii.hexlify(b[::-1].encode('utf-8')), 16) #b[::-1]意为倒置字符串return str(hex(pow(b, int(e, 16), int(m, 16))))[2:] #[2:]意为去掉16进制数前面的"0x"

加密算法都搞定了,那我们来实验一下:

首先断点,获取这次的请求参数和随机数分别是:

{"s":"1233","csrf_token":""}

NsZytjw6GDJpdS2F

然后通过代码加密:

from Crypto.Cipher import AESimport rsaimport randomimport base64import binasciie=r'010001'modules=r"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"key=r"0CoJUm6Qyw8W8jud"vi=r'0102030405060708'def encypt_bytes(search_params, key, vi):pad = chr(16 - len(search_params)%16)first = Truewhile (len(search_params) % 16 !=0) or first:search_params += pad.encode('utf-8')first = Falsecipher = AES.new(key, AES.MODE_CBC, vi)return base64.encodebytes(cipher.encrypt(search_params)).replace(b'n', b'').decode('utf-8')def encryptedString(b):b=int(binascii.hexlify(b[::-1].encode('utf-8')), 16)return str(hex(pow(b, 65537, int(modules, 16))))[2:]csrf_token = ''params = r'{"s":"1233","csrf_token":""}'first = encypt_bytes(params.encode('utf-8'), key, vi)print(params)print(first)print(encypt_bytes(first.encode('utf-8'), 'NsZytjw6GDJpdS2F', vi))print(encryptedString('NsZytjw6GDJpdS2F'))

得到的两个密文分别是

dssCbQpkBto5TYBj92Jz7zJ1K5H/Gg0nCZsw0XARaExW61yvA9jXsm+8PUGAtshN

25fb1dcb578e1f779050e3113ddcb11e35949490154b382171a45c307b307947d0faf566217441c1ae67ef66803d7f6454dfc8510c4dbbea30c61d2d28db7249ad176bdb28d2b7e1001acbf83e874e605e03ea73b26a7dea003c938adfd064c38ad988742df94610fd80615ee9d1ae25d8cea63f283030d318b3e739d317268d

然后释放断点,直接在Network选项卡看到这次请求的参数

完全一致,证明我们的加密算法是正确的。

打开Postman,我们测试一下可不可用。

选择form-data和x-www-form-urlencoded方式提交均不会返回任何数据:

注意url不能填错,否则会返回参数错误

以raw text方式直接提交可以获得返回数据:

因此,在编写代码的时候,要注意用raw方式直接提交字符串。

from Crypto.Cipher import AESfrom urllib import parseimport rsaimport randomimport stringimport base64import jsonimport requestsimport binasciie=r'010001'modules=r"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"key=r"0CoJUm6Qyw8W8jud"vi=r'0102030405060708'#header中要有UA,使用默认的UA会返回cheating__headers = {'Content-Type': 'application/x-www-form-urlencoded','User-Agent': 'PostmanRuntime/7.20.1','Accept': '*/*','Cache-Control': 'no-cache','Postman-Token': 'd3f8033f-6c33-456f-ba47-1774b597caa2','Host': '','Accept-Encoding': 'gzip, deflate','Content-Length': '612','Connection': 'keep-alive','Referer' : '/search/'}def get_music_list(name):csrf_token = (''.join(random.sample(string.ascii_letters + string.digits, 32))).lower()url = '/weapi/cloudsearch/get/web?csrf_token='+csrf_tokenpost_data = {'params': '','encSecKey': ''}search_params = r'{"hlpretag":"<span class="s-fc7">","hlposttag":"</span>","s":"'+name+r'","type":"1","offset":"0","total":"true","limit":"30","csrf_token":"'+csrf_token+r'"}'cipher_text_first = encypt_bytes(search_params.encode('utf-8'), key, vi)random16 = ''.join(random.sample(string.ascii_letters + string.digits, 16))post_data['params'] = encypt_bytes(cipher_text_first.encode('utf-8'), random16, vi)post_data['encSecKey'] = encryptedString(random16)#构建请求字符串post_text = parse.quote('params={}&encSecKey={}'.format(post_data['params'], post_data['encSecKey']), safe='=&')open('temp', 'w').write(search_params + 'n' + post_data['params']+'n'+post_data['encSecKey']+'n'+post_text)req = requests.post(url, data=post_text, headers=__headers)res = req.textjson.dump(res, open('temp.json', 'w'))return json.loads(res)def encypt_bytes(search_params, key, vi):pad = chr(16 - len(search_params)%16)first = Truewhile (len(search_params) % 16 !=0) or first:search_params += pad.encode('utf-8')first = Falsecipher = AES.new(key, AES.MODE_CBC, vi)return base64.encodebytes(cipher.encrypt(search_params)).replace(b'n', b'').decode('utf-8')def encryptedString(b):b=int(binascii.hexlify(b[::-1].encode('utf-8')), 16)return str(hex(pow(b, 65537, int(modules, 16))))[2:]print([music['name'] for music in get_music_list('jojo')['result']['songs']])

输出:

['JoJo', 'JoJo', 'Bloody Stream', '裏切り者のレクイエム', "il vento d'oro", 'JOJO x Lil One - 我不想留在这里 (Better Remix)', 'See You Again', 'JOJO', 'Awake', 'Cry On My Shoulder', '不染', 'JOJO', 'Apologize', 'Too Little Too Late (Radio Version)', 'Jojo', 'JoJo', 'Great Days', 'Disaster', 'Jojo', 'JOJO', 'JoJo', 'Jojo', 'Save My Soul', 'Vibe.', '欧若拉', 'Jojo', 'Jojo', 'Jojo', 'JoJo', 'Like That']

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。