环境:Win10
工具:Fiddler 4,Chrome浏览器
想做一个网易云音乐的下载地址解析,于是有了这篇文章,记录下过程。
开Fiddler截包
抓取到如下的包
POST http://music.163.com/weapi/song/enhance/player/url?csrf_token= HTTP/1.1
Host: music.163.com
Connection: keep-alive
Content-Length: 412
Origin: http://music.163.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Referer: http://music.163.com/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: _ntes_nnid=3ddf2945dfda076e60b4ee0162f3c0cd,1488291659575; _ntes_nuid=3ddf2945dfda076e60b4ee0162f3c0cd; vjuids=76bd0b1bc.15acc1b713d.0.73360b94f7956; vjlast=1489483035.1489483035.30; vinfo_n_f_l_n3=e441d5bafac7c9b7.1.0.1489483034993.0.1489483124982; JSESSIONID-WYYY=m%2FO4x3rjCh4e1xfBjmOCZ52hbV7rD7M9U77ZX9qn%2BMw5WeKyU0vnw1zGmpbn%5Ci5ZWmdaRmVjQromGw%2BxForePwG3mBf6jOy27vj1IMOv%5ClM3%2BXkUrPOeM7qPP9HhgO%2F%2Fd%5C1nWUq3mDUIicmtDvWMsUbEeWS%5CbNAJyUO8t%5CbHCy731FHp%3A1489661205597; _iuqxldmzr_=32; __utma=94650624.1301580310.1488291660.1489644772.1489659406.9; __utmb=94650624.4.10.1489659406; __utmc=94650624; __utmz=94650624.1489581581.6.4.utmcsr=baidu|utmccn=(organic)|utmcmd=organic
params=TsOlwX6jVxTvkYi3mFgJlVqczMblRwfL6sWG2wcRkYr5I2pB1GSC3eH4tLquNlPiXrBotId2T%2FI4UEwjRvbh3L%2FrlBKQ9de1vohxzueBBv%2FM01D4Fa%2BXhJ7jrSIhj%2FD2&encSecKey=747e088d0d3867619c340da1bedba0e60265ece84c0c1ef5ae81eff317b4afdaeed3666a9e1c3628abcabc0c602dba7685e1dfcc7ef82b6be24c35588a39941b6fe8e2a6fa751fded9c9b61c1cbf604d21b1cde54f244ea25c4db6673ef76151b8069b0ed5323c9ec3d2bace24b073b53e408ebb445c76a044ac797fa7c13be0
再看返回数据
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 16 Mar 2017 10:19:35 GMT
Content-Type: text/plain;charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Cache-Control: no-store
Pragrma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Content-Length: 352
{"data":[{"id":426881487,"url":"http://m10.music.126.net/20170316184438/37a51d1b5257f895cd867ff2dbd40186/ymusic/cba9/2013/eacb/50ab2466dcd90414959d84aa9c65dd9f.mp3","br":128000,"size":4575025,"md5":"50ab2466dcd90414959d84aa9c65dd9f","code":200,"expi":1200,"type":"mp3","gain":-3.1099,"fee":0,"uf":null,"payed":0,"flag":0,"canExtend":false}],"code":200}
返回的json中包含了mp3的真实地址
这里看看发包的数据
params=TsOlwX6jVxTvkYi3mFgJlVqczMblRwfL6sWG2wcRkYr5I2pB1GSC3eH4tLquNlPiXrBotId2T%2FI4UEwjRvbh3L%2FrlBKQ9de1vohxzueBBv%2FM01D4Fa%2BXhJ7jrSIhj%2FD2&encSecKey=747e088d0d3867619c340da1bedba0e60265ece84c0c1ef5ae81eff317b4afdaeed3666a9e1c3628abcabc0c602dba7685e1dfcc7ef82b6be24c35588a39941b6fe8e2a6fa751fded9c9b61c1cbf604d21b1cde54f244ea25c4db6673ef76151b8069b0ed5323c9ec3d2bace24b073b53e408ebb445c76a044ac797fa7c13be0
看不出任何的规律,故猜测是加密的数据,一般而言都是通过js进行加密的,那我们就去js里找一找
在浏览器中按F12调出开发者工具看到有一个core.js,打开看一看
通过搜索关键词params可以找到如下代码
var bua = window.asrsea(JSON.stringify(bl), bbZ(["流泪", "强"]), bbZ(cnb.md), bbZ(["爱心", "女孩", "惊恐", "大笑"]));
bf.data = bm.eX({
params: bua.encText,
encSecKey: bua.encSecKey
})
这里看到params和encSecKey是通过window.asrsea函数运算得到的,那继续找一下这个函数
!function () {
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
}
function e(a, b, d, e) {
var f = {};
return f.encText = c(a + e, b, d),
f
}
window.asrsea = d,
window.ecnonasr = e
}
这里看到asrsea函数就是d函数,并且传入了4个参数
对四个参数进行分析
利用fiddler的AutoResponder可以方便我们本地调试js
在core.js中加入这段
window.console.info(bl);
var bua = window.asrsea(JSON.stringify(bl), bbZ(["流泪", "强"]), bbZ(cnb.md), bbZ(["爱心", "女孩", "惊恐", "大笑"]));
window.console.warn(bua.encText);
bf.data = bm.eX({
params: bua.encText,
encSecKey: bua.encSecKey
})
清除浏览器的缓存,刷新页面,可以看到开发者工具console里出现了信息
{“ids”:”[426881487]”,”br”:128000,”csrf_token”:”“}
这个是参数一
用同样的方法获取到四个参数
参数一
{"ids":"[426881487]","br":128000,"csrf_token":""}
参数二
010001
参数三
00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
参数四
0CoJUm6Qyw8W8jud
显然encText和params是等值的,那看一下encText是如何计算的
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
}
调用了b方法,再去看看b
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()
}
这是一个CBC模式的AES加密,偏移量是 “0102030405060708”
第一个参数是被加密文本
第二个参数是定值0CoJUm6Qyw8W8jud
回到b方法
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
}
encText进行了两次加密,第二次是把第一次加密的结果再加密一次
那么第二次加密的key值i是怎么获得的,可以看到调用了a方法,再去看看a
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
}
这是一个取随机字符的方法,参数决定了取出多少位,既然是随机数我们就可以自己随便写,只要是16位即可
至此我们分析出了params的加密方式
再来看看encSecKey
h.encSecKey = c(i, e, f),
调用了c方法,但是c方法传递的参数是固定的(i由我们自己决定),那么这个encSecKey也就是固定的
至此两个参数是如何得到的我们都分析出来了
附Java计算params值代码
/**
* Created by PVer on 2017/3/19.
*/
import java.util.Scanner;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import java.net.URLEncoder;
public class AES {
// 加密
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec("0102030405060708".getBytes());//使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(sSrc.getBytes());
return new BASE64Encoder().encode(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
// 解密
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("0102030405060708"
.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
public static String get_params(String text) throws Exception {
String first_key = "0CoJUm6Qyw8W8jud";
String second_key = "FFFFFFFFFFFFFFFF";
String h_encText = AES.Encrypt(text, first_key);
h_encText = AES.Encrypt(h_encText, second_key);
return h_encText;
}
public static String get_encSecKey() {
String encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c";
return encSecKey;
}
public static void main(String[] args) throws Exception {
/*
* 加密用的Key 可以用26个字母和数字组成,最好不要用保留字符,虽然不会错,至于怎么裁决,个人看情况而定
* 此处使用AES-128-CBC加密模式,key需要为16位。
*/
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要解析的音乐ID:");
String first_param = "{\"ids\":\"[" + scanner.next() + "]\",\"br\":128000,\"csrf_token\":\"\"}";
System.out.println("params=" + URLEncoder.encode(AES.get_params(first_param), "UTF-8") + "&encSecKey=" + AES.get_encSecKey());
}
}