解析網(wǎng)易云音樂的加密方式

找到參數(shù)的加密方法

首先我們先看評(píng)論的加載方式宵喂,打開一首音樂的主頁(yè)框产,然后打開開發(fā)工具的Network選項(xiàng)道批,點(diǎn)擊評(píng)論的翻頁(yè)按鈕,可以看到第一個(gè)請(qǐng)求就是請(qǐng)求下一頁(yè)的評(píng)論:

comment.png

我們分析一下這個(gè)請(qǐng)求骑冗,先看它的url赊瞬,請(qǐng)求多次之后發(fā)現(xiàn)R_SO_4_在請(qǐng)求評(píng)論時(shí)是固定的,483671599則是歌曲的id贼涩,url還有一個(gè)參數(shù)csrf_token森逮,看這個(gè)名字像是防止跨站攻擊的,但是它一直是空的磁携。然后就是POST里面的參數(shù)paramsencSecKey褒侧,這兩個(gè)參數(shù)是關(guān)鍵,接下來我們要重點(diǎn)分析它。
我們?cè)陂_發(fā)工具對(duì)encSecKey進(jìn)行全局搜索闷供,發(fā)現(xiàn)它只出現(xiàn)在一個(gè)文件中:
search.png

點(diǎn)擊搜索結(jié)果烟央,打開文件并美化后發(fā)現(xiàn),這2處地方歪脏,一個(gè)只是簡(jiǎn)單對(duì)結(jié)果賦值疑俭,params通過bAQ8I.encText而來,encSecKey通過bAQ8I.encSecKey而來婿失,而另一個(gè)則是有具體函數(shù)調(diào)用钞艇,而這個(gè)就是我們的突破口。

function a(a) {
    var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
    c = "";
    for (d = 0; a > d; d += 1) e = Math.random() * b.length,
    e = Math.floor(e),
    c += b.charAt(e);
    return c
}
function b(a, b) {
    var c = CryptoJS.enc.Utf8.parse(b),
    d = CryptoJS.enc.Utf8.parse("0102030405060708"),
    e = CryptoJS.enc.Utf8.parse(a),
    f = CryptoJS.AES.encrypt(e, c, {
        iv: d,
        mode: CryptoJS.mode.CBC
    });
    return f.toString()
}
function c(a, b, c) {
    var d, e;
    return setMaxDigits(131),
    d = new RSAKeyPair(b, "", c),
    e = encryptedString(d, a)
}
function d(d, e, f, g) {
    var h = {},
    i = a(16);
    return h.encText = b(d, g),
    h.encText = b(h.encText, i),
    h.encSecKey = c(i, e, f),
    h
}

我們先簡(jiǎn)單分析一下這幾個(gè)函數(shù)豪硅,可以看到最后的賦值是在d(d,e,f,g)這個(gè)函數(shù)內(nèi)完成的哩照,它首先調(diào)用了a(a),可以看出這個(gè)函數(shù)的作用是生成一個(gè)長(zhǎng)度為16的隨機(jī)字符串懒浮;然后encText這個(gè)參數(shù)通過2次調(diào)用b(a,b)完成飘弧,這個(gè)函數(shù)的作用是進(jìn)行AES加密;最后encSecKey是調(diào)用c(i,e,f)完成砚著,這個(gè)函數(shù)的作用是進(jìn)行RSA加密次伶。
通過上面的代碼可以看出,params的生成需要d, g,i這3個(gè)參數(shù)稽穆,前2個(gè)是函數(shù)傳進(jìn)來的冠王,最后一個(gè)是隨機(jī)生成的。而encSecKey的生成則需要e, f,i這3個(gè)參數(shù)舌镶,前2個(gè)是函數(shù)傳進(jìn)來的版确,最后一個(gè)和前面相同。
所以理論上我們知道了d,e,f,g這4個(gè)參數(shù)就可以構(gòu)造請(qǐng)求了乎折,我們?cè)?code>d函數(shù)加斷點(diǎn),繼續(xù)點(diǎn)擊下一頁(yè)侵歇,可以在斷點(diǎn)處調(diào)試骂澄,看到傳入的參數(shù):

param.png

試了幾次后我們發(fā)現(xiàn),無論是同一會(huì)話的新請(qǐng)求惕虑,還是新會(huì)話中的請(qǐng)求坟冲,e,f,g的值都是不變的,所以可以初步斷定這3個(gè)值是固定的溃蔫,唯一有改變的就是d的值健提,所以我們只需要在請(qǐng)求時(shí)構(gòu)造好就行了。

參數(shù)i的生成

只需要簡(jiǎn)單的生成16位隨機(jī)字符串即可

import random
from string import ascii_letters, digits

_charset = ascii_letters + digits

def rand_char(num=16):
    return ''.join(random.choice(_charset) for _ in range(num))

params的生成

從代碼可以看出伟叛,2次AES加密中私痹,初始向量都是0102030405060708,加密模式都是CBC加密,不同的是第一次加密中紊遵,d作為message账千,g作為key來加密;第二次加密中暗膜,把第一次加密結(jié)果作為message匀奏,i作為key來加密。我們可以通過Crypto.Cipher中的AES實(shí)現(xiàn)学搜,

import base64
from Crypto.Cipher import AES


def aes_encrypt(msg, key, iv='0102030405060708'):
    def padded(msg):
        pad = 16 - len(msg) % 16
        return msg + pad * chr(pad)

    msg = padded(msg)
    cryptor = AES.new(key, IV=iv, mode=AES.MODE_CBC)
    text = cryptor.encrypt(msg)
    text = base64.b64encode(text)
    return text


def gen_params(d, g, i):
    text = aes_encrypt(d, g)
    text = aes_encrypt(text, i)
    return text

encSecKey的生成

這個(gè)參數(shù)通過RSA算法生成娃善,其中i作為message,e,f是加密時(shí)用到的參數(shù)瑞佩。
在這里稍微解釋一下RSA算法聚磺,算法選取2個(gè)很大的質(zhì)數(shù)p,q,得到它們的乘積n钉凌,然后選取e,d滿足e*d = 1 mod (p-1)(q-1)咧最,加密時(shí)text=(msg^e)%n,解密時(shí)msg=(text^d)%n御雕,在這個(gè)函數(shù)里e就相當(dāng)于算法里的e矢沿,f相當(dāng)于算法里的n
還有一點(diǎn)需要注意酸纲,encSecKey是一個(gè)完全由16進(jìn)制數(shù)組成捣鲸,但是在加密模塊中一般都是返回byte流,然后通過base64編碼(長(zhǎng)度是原來的4/3)闽坡,而像這種的應(yīng)該是把byte流通過16進(jìn)制表示出來(長(zhǎng)度是原來的2倍)栽惶。
下面就是用python實(shí)現(xiàn)的時(shí)候了,我們可以通過Crypto.PublicKeyRSAconstruct方法實(shí)現(xiàn)疾嗅。

# 錯(cuò)誤版本
import binascii
from Crypto.PublicKey import RSA

cryptor = RSA.construct((0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7, 0x10001L))
text = cryptor.encrypt(msg, '')[0]
text = binascii.b2a_hex(text)  # byte流轉(zhuǎn)為16進(jìn)制

但是這時(shí)候問題出現(xiàn)了外厂,上面的代碼加密出來的結(jié)果和實(shí)際不符合,這樣看來網(wǎng)易云的RSA加密和標(biāo)準(zhǔn)的有些不同代承,所以我們要深入到encryptedString這個(gè)方法進(jìn)行調(diào)試汁蝶。

function encryptedString(a, b) {
  for (var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0; d > e; )
    c[e] = b.charCodeAt(e),
    e++;
  for (; 0 != c.length % a.chunkSize; )
    c[e++] = 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)  // here
      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 + " "
  }
  return g.substring(0, g.length - 1)
}

通過代碼可以看出,c數(shù)組是b字符串轉(zhuǎn)成的數(shù)組论悴,然后在for循環(huán)中掖棉,c數(shù)組從左到右是從低位加到高位的,比如123456膀估,1是加在低位幔亥,6是加在高位,這和平常有些不一樣察纯。
這樣看來似乎需要把要加密的消息先翻轉(zhuǎn)一下帕棉,然后再進(jìn)行加密针肥,測(cè)試之后發(fā)現(xiàn)也確實(shí)如此,實(shí)現(xiàn)如下:

import binascii
from Crypto.PublicKey import RSA

def rsa_encrypt(msg):
    cryptor = RSA.construct((0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7, 0x10001L))
    text = cryptor.encrypt(msg[::-1], '')[0]
    text = binascii.b2a_hex(text)
    return text

事實(shí)上笤昨,也可以自己來實(shí)現(xiàn)它的加密方式text=(msg^e)%n祖驱,只是自己實(shí)現(xiàn)的方式效率會(huì)比較低

def rsa_encrypt2(msg):
    msg = binascii.b2a_hex(msg[::-1])
    msg = int(msg, 16)
    text = 1
    for _ in range(0x10001):
        text *= msg
        text %= 0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
    return format(text, 'x')
compare.png

最終實(shí)現(xiàn)

import base64
import binascii
import json
import random
import requests

from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from string import ascii_letters, digits

_charset = ascii_letters + digits


def rand_char(num=16):
    return ''.join(random.choice(_charset) for _ in range(num))


def aes_encrypt(msg, key, iv='0102030405060708'):
    def padded(msg):
        pad = 16 - len(msg) % 16
        return msg + pad * chr(pad)

    msg = padded(msg)
    cryptor = AES.new(key, IV=iv, mode=AES.MODE_CBC)
    text = cryptor.encrypt(msg)
    text = base64.b64encode(text)
    return text


def gen_params(d, i):
    text = aes_encrypt(d, '0CoJUm6Qyw8W8jud')
    text = aes_encrypt(text, i)
    return text


def rsa_encrypt(msg):
    cryptor = RSA.construct((0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7, 0x10001L))
    text = cryptor.encrypt(msg[::-1], '')[0]
    text = binascii.b2a_hex(text)
    return text


def encrypt(query):
    query = json.dumps(query)
    rand_i = rand_char(16)
    params = gen_params(query, rand_i)
    enc_sec_key = rsa_encrypt(rand_i)
    data = {
        'params': params,
        'encSecKey': enc_sec_key
    }
    return data

if __name__ == '__main__':
    music_id = '483671599'
    url = 'http://music.163.com/weapi/v1/resource/comments/R_SO_4_{}?csrf_token='.format(music_id)
    headers = {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
        'Connection': 'keep-alive',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Host': 'music.163.com',
        'Origin': 'http://music.163.com',
        'Referer': 'http://music.163.com/',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
    }
    query = {
        'rid': 'R_SO_4_{}'.format(music_id),
        'offset': '0',
        'total': 'true',  # 第一頁(yè)時(shí)為true,其他頁(yè)為false
        'limit': '20',
        'csrf_token': ''
    }
    data = encrypt(query)

    r = requests.post(url, data=data, headers=headers)
    print(r.content)
    for item in r.json()['comments']:
        print(item['content'])
result.png

一個(gè)套路

通過代碼我們可以看見encSecKey是由i決定的瞒窒,但是這個(gè)參數(shù)是瀏覽器這邊隨機(jī)生成的捺僻,所以其實(shí)是可以寫死的,這樣一來encSecKey就成了一個(gè)固定值崇裁,只需要處理params這個(gè)參數(shù)匕坯,當(dāng)然,會(huì)不會(huì)因?yàn)?code>encSecKey總是不變而被封IP什么的我就不知道了

其它

由于RSA是非對(duì)稱加密拔稳,我們無法通過encSecKey解密出i葛峻,沒有i也就無法解密params,所以也就只能對(duì)每個(gè)接口進(jìn)行斷點(diǎn)調(diào)試巴比,觀察請(qǐng)求的構(gòu)造术奖,這里提供幾個(gè)常用接口的參數(shù)

  1. 歌曲評(píng)論

url:http://music.163.com/weapi/v1/resource/comments/R_SO_4_483671599?csrf_token=
d: {"rid":"R_SO_4_483671599","offset":"20","total":"false","limit":"20","csrf_token":""}

  1. 歌曲歌詞

url:http://music.163.com/weapi/song/lyric?csrf_token=
d:{"id":"483671599","lv":-1,"tv":-1,"csrf_token":""}

  1. 歌單評(píng)論

url:http://music.163.com/weapi/v1/resource/comments/A_PL_0_2003824512?csrf_token=
d:{"rid":"A_PL_0_2003824512","offset":"0","total":"true","limit":"20","csrf_token":""}

  1. 搜索

url:http://music.163.com/weapi/cloudsearch/get/web?csrf_token=

搜索單曲:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}

搜索歌手:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"100","offset":"0","total":"true","limit":"90","csrf_token":""}

搜索專輯:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"10","offset":"0","total":"true","limit":"75","csrf_token":""}

搜索MV:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"1004","offset":"0","total":"true","limit":"20","csrf_token":""}

搜索歌詞:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"1006","offset":"0","total":"true","limit":"30","csrf_token":""}

搜索歌單:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"1000","offset":"0","total":"true","limit":"30","csrf_token":""}

搜索主播電臺(tái):{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"1009","offset":"0","total":"true","limit":"30","csrf_token":""}

搜索用戶:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"愛","type":"1002","offset":"0","total":"true","limit":"30","csrf_token":""}

最后,文章僅供學(xué)習(xí)轻绞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末采记,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子政勃,更是在濱河造成了極大的恐慌唧龄,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奸远,死亡現(xiàn)場(chǎng)離奇詭異既棺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)懒叛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門丸冕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人薛窥,你說我怎么就攤上這事胖烛。” “怎么了拆檬?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)妥凳。 經(jīng)常有香客問我竟贯,道長(zhǎng),這世上最難降的妖魔是什么逝钥? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任屑那,我火速辦了婚禮拱镐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘持际。我一直安慰自己沃琅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布蜘欲。 她就那樣靜靜地躺著益眉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姥份。 梳的紋絲不亂的頭發(fā)上郭脂,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音澈歉,去河邊找鬼展鸡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛埃难,可吹牛的內(nèi)容都是我干的莹弊。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼涡尘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼忍弛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悟衩,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤剧罩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后座泳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惠昔,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年挑势,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镇防。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潮饱,死狀恐怖来氧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情香拉,我是刑警寧澤啦扬,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站凫碌,受9級(jí)特大地震影響扑毡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盛险,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一瞄摊、第九天 我趴在偏房一處隱蔽的房頂上張望勋又。 院中可真熱鬧,春花似錦换帜、人聲如沸楔壤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹲嚣。三九已至,卻和暖如春跳座,著一層夾襖步出監(jiān)牢的瞬間端铛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工疲眷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禾蚕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓狂丝,卻偏偏與公主長(zhǎng)得像换淆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子几颜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理倍试,服務(wù)發(fā)現(xiàn),斷路器蛋哭,智...
    卡卡羅2017閱讀 134,716評(píng)論 18 139
  • # 一度蜜v3.0協(xié)議 --- # 交互協(xié)議 [TOC] ## 協(xié)議說明 ### 請(qǐng)求參數(shù) 下表列出了v3.0版協(xié)...
    c5e350bc5b40閱讀 652評(píng)論 0 0
  • 用過很多播放器县习,之前一直是酷我,偶爾QQ谆趾。但是網(wǎng)易云音樂出來后毅然變成了他的忠實(shí)用戶躁愿。精確推薦和樂評(píng)都很贊!安利了...
    聽城閱讀 2,442評(píng)論 1 5
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法沪蓬,類相關(guān)的語法彤钟,內(nèi)部類的語法,繼承相關(guān)的語法跷叉,異常的語法逸雹,線程的語...
    子非魚_t_閱讀 31,667評(píng)論 18 399
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 11,010評(píng)論 6 13