首页 网易云音乐API分析(附C#版教程源码)

网易云音乐API分析(附C#版教程源码)

这里只教大家分析获取网易云音乐播放的mp3地址,其他的接口可以自己分析所得,毕竟授人予鱼不如授人予渔,其他的都差不多,所以可以自己分析。如果分析有问题, 我们可以交流。


准备工作

  1. Chrome浏览器   有相关的浏览器,能按照教程操作一样可以,不一定是Chrome浏览器
  2. Postman  可以暂时不下载安装,等到看到教程下面需要再安装,也许你有相对应工具

第一步 知道要请求什么

打开某网址 例如 http://music.163.com/#/song?id=484730184 按下图操作

image.png

通过f12我们可以看到改地址是我们需要获取的地址,但是我们肯定是需要代码来获取的而不是每次f12,不然太丢程序猿的脸了。

由上图可见 是通过该地址http://music.163.com/weapi/song/enhance/player/url?csrf_token=请求的mp3地址的。而请求的参数是以下

params:
17QdKOQMbo7HRtDSgu8AH9/aqz1hIgecImKEQ4b9SaQLbcofwAqskQN0whFLSQmvmag6BMUEjxepgxTOcOVCg1VsYhUTktpTswMuaPJ0CPAG91vAK/GdlI+9IXuOrTkh
encSecKey:
087323050e053a1fbf5d16f4a698d2f85bed542e59baba9aecffa0af0068821ff69d818e1b593ee9d04c6d33ca94cd6b4e6696dc93bdf02e296b0823fca25573b0f1469d52bfe97cd048021d048426a31d1ccb519dee290c0971d7b73fe9fcd5e461bd797a7342456f6548982a63923e970e5d3dc4336ef38c89438d8140ba60

这么一长串不用说肯定就是加密过的,不过我之前(就是两三个月前)还在网上找到过不加密的,但是找到没过多久也不行了。所以需要分许这参数的加密方法。那么我们如何下手呢,一般遇到这种加密的都是 js 加密好的,所以我们需要分析js ,有些人肯定说,js功底不行,其实不需要什么功底,语法都是互通的对于js加密你只需要认识 rsa aes md5 bas64 这几个单词就可以了,因为目前所有的js无非这些加密所得到的,所以你知道这些在你会的语言上写出对应的加密方法即可。


第二步 找到对应的加密的方法

image.png


点击 Sources,可以看到有很多请求的数据,这里包含 js css image 以及页面,基本上大的公司加密的方法都是放在一个单独的js文件中,所以我们可以每一个个展开只选择js文件 然后搜索参数 params 或者 encSecKey 其中一个即可,当然你也可以搜索请求地址 http://music.163.com/weapi/song/enhance/player/url?csrf_token= 但是一般不建议这么做,因为大多数的请求地址都是各种变量拼接的,而且有时候请求地址的变量申明跟直接我们需要找的参数离的十万八千里,更何况变量都是加密过的只有 a,b,c,d没法找到,但是因为请求的参数是这两个,再怎么加密这两个都会出现再js中的,如果你第一次翻遍了所有的js都没看到,那么就找一下页面,还是没有就刷新浏览器,重新点击再找,别问我问什么需要刷新浏览器,因为有时候就是第一次按f12的时候,里面就是没有我们需要的js,刷新几次就有了。

image.png

可以看到图中这个core.jshttp://s3.music.126.net/sep/s/2/core.js?51c175b69f779986b5f2b7445b85c7b1encSecKey有三个,那应该就是他了,最后再点击红色箭头所指的就可以美化一下代码啦,不然你就只能看着一坨坨的 image.png

通过搜索就可以看到这里有我们需要的两个参数,那么接下来就只需要研究这两个参数所在的上面一部分代码即可,其他代码都无需再管。


第三步 分析加密方法

image.png

通过截图中代码 就能看出 params 以及 encSecKey 两个参数来自于 bAM1x 变量 而该变量就在上一行申明的,所以我们只需要在这里设置一个断点即可,然后回到页面上点击播放,之后就会进入断点

image.png

var bAM1x = window.asrsea(JSON.stringify(j8b), bnQ8I(["流泪", "强"]), bnQ8I(Mc8U.md), bnQ8I(["爱心", "女孩", "惊恐", "大笑"]));

通过控制台输出可知道

bnQ8I(["流泪", "强"]) => "010001"

bnQ8I(Mc8U.md) => "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"

bnQ8I(["爱心", "女孩", "惊恐", "大笑"]) => "0CoJUm6Qyw8W8jud"

经过多次输出这几个参数都是固定的,至少现在多次输出是一样的,所以不管,如果后面发现会变化的时候,我们再来看。

以及第一个参数 JSON.stringify(j8b) => "{"ids":"[484730184]","br":128000,"csrf_token":""}"   有点常识的都知道这个参数ids对应的当前歌曲的id,所以接下来f11进入 window.asrse 方法内部查看内部加密是什么,注意,可能你按f11不是进入的这个方法而是 bnQ8I 的方法内部,因为需要先把参数的结果算出来,window.asrse 对应的就是下面这个方法

image.png

通过该方法可以看出,内部使用了 a b c三个方法,且两次b方法,

也就是下面几个方法

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
    }

光只看这几个方法,就可以看出,a方法是产生16位随机数,b方法是进行AES加密 c方法是进行 RSA加密,所以结合上面的,也就是说,参数params是经过两次aes加密得到的,而encSecKey是RSA加密所得。并且都使用到了a方法产生的随机数。


第四步 进一步分析

通过第三步可以看出几个方法是干嘛的,由于a方法是随机数,所以暂时不管,我们可以认为是一个固定的16位数,还是那句话如果我们分析完,进行自己编写代码的时候发现还是请求不出来数据,我们再回头分析。

所以我们可以再b和c方法内部设置断点,运行看看进来的参数分别是什么。其实由d方法即 window.asrsea 内部也可以看出来是把把第一个参数(含有歌曲id) 是b方法的第一个参数


b方法 两次AES加密 计算params参数

b方法内部既然是AES加密 所以至少需要三个参数 

  1. 需要加密的数据         ->      CryptoJS.enc.Utf8.parse(a)                                    a -> {"ids":"[484730184]","br":128000,"csrf_token":""}
  2. 加密的key                  ->      CryptoJS.enc.Utf8.parse("0102030405060708")   
  3. vi向量                         ->      CryptoJS.enc.Utf8.parse(b)                                    b -> 0CoJUm6Qyw8W8jud

所以我们编写对应的AES加密代码 我这里使用的是C#,有人问想用其他编程语言写怎么办,那就去百度或者谷歌方法直接拿来用即可,只要你的加密数据和key以及vi跟我的一样 结果跟我下面的一样就代表是ok的

/// <summary>
/// AES加密
/// </summary>
/// <param name="plaintextData">明文的数据</param>
/// <param name="key">key</param>
/// <param name="iv">iv</param>
/// <returns></returns>
private static string AesEncrypt(string plaintextData, string key, string iv = "0102030405060708")
{
	try
	{
		if (string.IsNullOrEmpty(plaintextData)) return null;
		Byte[] toEncryptArray = Encoding.UTF8.GetBytes(plaintextData);

		RijndaelManaged rm = new RijndaelManaged
		{
			Key = Encoding.UTF8.GetBytes(key),
			IV = Encoding.UTF8.GetBytes(iv),
			Mode = CipherMode.CBC
		};

		ICryptoTransform cTransform = rm.CreateEncryptor();
		Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

		return Convert.ToBase64String(resultArray, 0, resultArray.Length);
	}
	catch (Exception ex)
	{
		return "";
	}
}

而调用的方法是  

  1. 加密明文 => {"ids":"[484730184]","br":128000,"csrf_token":""}
  2. key         =>  0CoJUm6Qyw8W8jud (第一次AES加密这个是固定的)
  3. vi            =>  0102030405060708  (这个两次加密都是固定的)

static void Main(string[] args)
{
	var param = AesEncrypt("{\"ids\":\"[484730184]\",\"br\":128000,\"csrf_token\":\"\"}", "0CoJUm6Qyw8W8jud");
	Console.WriteLine(param);
	Console.WriteLine();

	Console.ReadKey();
}

第一次AES加密的得出的结果是  M+sMQRArh9OdctOmgLB+lcKAiWtieKy6+rcGKYzo8t9JXhGHXdUtpVgsRHsgiVyeDbtkFD8y3lsFtKgXoWVGVA== 

接下来第二次加密

  1. 加密明文 是第一次加密的结果 =>  M+sMQRArh9OdctOmgLB+lcKAiWtieKy6+rcGKYzo8t9JXhGHXdUtpVgsRHsgiVyeDbtkFD8y3lsFtKgXoWVGVA==
  2. key  此次这个就是16位随机数         =>  a8LWv2uAtXjzSfkQ 

代码是 

static void Main(string[] args)
{
	var param = AesEncrypt("{\"ids\":\"[484730184]\",\"br\":128000,\"csrf_token\":\"\"}", "0CoJUm6Qyw8W8jud");
	Console.WriteLine(param);
	Console.WriteLine(param == "M+sMQRArh9OdctOmgLB+lcKAiWtieKy6+rcGKYzo8t9JXhGHXdUtpVgsRHsgiVyeDbtkFD8y3lsFtKgXoWVGVA==");
	Console.WriteLine();

	param = AesEncrypt(param, "a8LWv2uAtXjzSfkQ");
	Console.WriteLine(param);
	Console.WriteLine();

	Console.ReadKey();
}

第二次AES加密的结果是 OiuZUGYGoept41ByZhlqJBEMfoYG3rL6LAxjkkwozOTlUpEnM2Z96d2f+3Ahvs3YPLMqgABE1w3mmBW93fNp4U871KcUjLRYZRMJul9PsG+P4vqvGupfWTRzp+3B5LUQ 多次运行结果都是一样的 所以别的语言写的加密只要结果跟我这个一样即可,需要注意的第二次加密的key必须跟我这个一样而不是随机数 否则结果跟我不一致的。


c方法 一次RSA加密 计算encSecKey参数

c方法内部是一个RSA加密的方法,按照AES加密一样加密即可,但是因为我这里知道c方法不是必须的所以我下面就没写了相关的代码了,可能有人会疑问,为什么不是必须的,你怎么知道不是必须的。确实,如果一开始分析我也不知道,肯定会去写一个RSA加密方法然后把encSecKey计算出来,但是经过我再进一步分析的得到的这个encSecKey参数是可以固定的,而RSA加密的结果就是encSecKey 所以也就RSA不是必须的。


最后小结分析

既然encSecKey参数是固定的,那这个参数作用是什么,网易肯定不会傻逼放个没用的参数,我们知道AES加密是把歌曲id用16位数随机数作为key两次aes加密 得到第一个参数params,那么网易肯定需要解密得到歌曲id才能返回歌曲给我们,所以encSecKey这个参数是把16位数随机数加密然后传输到服务端,然后服务端RSA解密通过得到16位随机数当作AES解密的key从而得到歌曲的id然后返回对应的mp3播放地址,字幕(lyric)等信息给我们的。所以整个加密解密请求流程就是这样子的。

测试校验

已知

  1. 歌曲 http://music.163.com/#/song?id=518686034 => 518686034
  2. 请求地址 => http://music.163.com/weapi/song/enhance/player/url?csrf_token=
  3. 随机16位 => a8LWv2uAtXjzSfkQ
  4. encSecKey => 2d48fd9fb8e58bc9c1f14a7bda1b8e49a3520a67a2300a1f73766caee29f2411c5350bceb15ed196ca963d6a6d0b61f3734f0a0f4a172ad853f16dd06018bc5ca8fb640eaa8decd1cd41f66e166cea7a3023bd63960e656ec97751cfc7ce08d943928e9db9b35400ff3d138bda1ab511a06fbee75585191cabe0e6e63f7350d6

把歌曲id放入上面的代码中得到加密后的params => G5hSmKGdgA6s0OomycbltuxmSsPdtRcqj5NQo9PCJuU7biZ5aQlXVdpYm+KHZpAD901ERAVrqxZlFLsB/V+/trL2gSrycmvth+0Pr8277bs2SjdC8kj6xQWhTu1nrXX1

打开 Postman 

image.png

注意是post请求 以及Content-Type必须指定 application/x-www-form-urlencoded,否则请求不到数据,切换到body选择raw输入参数

params=G5hSmKGdgA6s0OomycbltuxmSsPdtRcqj5NQo9PCJuU7biZ5aQlXVdpYm%2BKHZpAD901ERAVrqxZlFLsB%2FV%2B%2FtrL2gSrycmvth%2B0Pr8277bs2SjdC8kj6xQWhTu1nrXX1&encSecKey=2d48fd9fb8e58bc9c1f14a7bda1b8e49a3520a67a2300a1f73766caee29f2411c5350bceb15ed196ca963d6a6d0b61f3734f0a0f4a172ad853f16dd06018bc5ca8fb640eaa8decd1cd41f66e166cea7a3023bd63960e656ec97751cfc7ce08d943928e9db9b35400ff3d138bda1ab511a06fbee75585191cabe0e6e63f7350d6

可能你直接通过上面代码得到的params参数放进去是获取不到数据的,那是因为需要把参数的值进行UrlEncode一下才可以

image.png



最后小结

本来打算是把源码放进来,但是想了想教程都有了,也包含C#加密代码在上面教程也有了,剩下的也都是一些组合。放进来好像也没必要。所以获取字幕,获取评论等接口就需要各位自己去玩了,授人予鱼不如授人予渔。


附超大GIF操作步骤

Gif演示抓取步骤

除另有声明外,本文章网易云音乐API分析(附C#版教程源码)采用 知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 进行许可。

评论
目录