网易云音乐直链解析
本文详细介绍了提取网易云音乐前端歌曲播放链接的分析过程。
在写这篇文章时刚刚学习前端不久,代码写的比较烂……总之重点在如何破解网易云音乐的加密方式。
后面有一篇博文展示了如何写一个真正可用的 API ,但由于本博客正在搬迁,搬运完成后会在这里把链接贴出来。
技术栈
- Koa基础
推荐一篇博客,把koa讲的非常易懂,几乎看懂他提供的例子后koa就会得差不多了,如果有时间,我可能也会写一篇教程。
- JavaScript基础
- HTML基础
用到的Node.js模块
- koa
- koa-route
- axios
- crypto-js
如果你用的WebStorm,直接写
const Koa = require("koa");
const querystring = require("querystring");
const CryptoJS = require("crypto-js");
const axios = require("axios");
const fs = require("fs");
const route = require("koa-route");
const app = new Koa();
即可,WebStorm会自动提示你安装。
其实自己安装也很简单,先切换到工作目录,用cmd或shell运行:
npm install koa
npm install koa-route
npm install axios
npm install crypto-js
实现
1. 抓包、定位加密代码
首先打开网易云音乐网页版,进入任意一首歌,打开浏览器的DevTools,选择Network,点击播放,稍加分析,不难看出,这个post
请求是用来获取音乐链接。
切换到调用栈(Initiator),看看发送它的函数在哪
打开,发现是一大坨看不懂的代码
看来这样走不通,那就搜搜post请求的data吧,Ctrl
+F
,搜encSecKey
嗯,完美,很显然,这里的两个参数来自第13297行(可能你看到的行数和我不一样)的window.asrsea()
函数
先刷新一下,再在那一行打个断点,点击播放
断点触发,进入window.asrsea()
函数
再在那个d(d,e,f,g)
函数的第一行打个断点,可以看到这就是我们要找的加密函数。
2. 分析加密代码
在左边的局部变量中看出,d
保存的是一个字符串化的json,保存着要获取的歌曲id
{
"id": [
32102297
],
"level": "standard",
"encodeType": "aac",
"csrf_token": ""
}
经过多次测试,e
是一个定值:"010001"
,来自["流泪", "强"]
两个表情转换为对应的代码,转换映射如下:
{
"色": "00e0b", "流感": "509f6", "这边": "259df", "弱": "8642d",
"嘴唇": "bc356", "亲": "62901", "开心": "477df", "呲牙": "22677",
"憨笑": "ec152", "猫": "b5ff6", "皱眉": "8ace6", "幽灵": "15bb7",
"蛋糕": "b7251", "发怒": "52b3a", "大哭": "b17a8", "兔子": "76aea",
"星星": "8a5aa", "钟情": "76d2e", "牵手": "41762", "公鸡": "9ec4e",
"爱意": "e341f", "禁止": "56135", "狗": "fccf6", "亲亲": "95280",
"叉": "104e0", "礼物": "312ec", "晕": "bda92", "呆": "557c9",
"生病": "38701", "钻石": "14af6", "拜": "c9d05", "怒": "c4f7f",
"示爱": "0c368", "汗": "5b7a4", "小鸡": "6bee2", "痛苦": "55932",
"撇嘴": "575cc", "惶恐": "e10b4", "口罩": "24d81", "吐舌": "3cfe4",
"心碎": "875d3", "生气": "e8204", "可爱": "7b97d", "鬼脸": "def52",
"跳舞": "741d5", "男孩": "46b8e", "奸笑": "289dc", "猪": "6935b",
"圈": "3ece0", "便便": "462db", "外星": "0a22b", "圣诞": "8e7",
"流泪": "01000", "强": "1", "爱心": "0CoJU", "女孩": "m6Qyw",
"惊恐": "8W8ju", "大笑": "d"
}
f
同样是定值,来自一下表情转换为代码
[
"色", "流感", "这边", "弱", "嘴唇", "亲", "开心", "呲牙", "憨笑",
"猫", "皱眉", "幽灵", "蛋糕", "发怒", "大哭", "兔子", "星星", "钟情",
"牵手", "公鸡", "爱意", "禁止", "狗", "亲亲", "叉", "礼物", "晕",
"呆", "生病", "钻石", "拜", "怒", "示爱", "汗", "小鸡", "痛苦",
"撇嘴", "惶恐", "口罩", "吐舌", "心碎", "生气", "可爱", "鬼脸",
"跳舞", "男孩", "奸笑", "猪", "圈", "便便", "外星", "圣诞"
]
g
同上,是["爱心", "女孩", "惊恐", "大笑"]
转换为代码
所以
e = "010001"
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
嗯,常量搞清楚了,再看看加密方法
!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
}();
enText
就是post请求里的params
参数,来自b函数加密两次
encSecKey
来自c函数加密一次