网易云音乐API分析 weapi(早期版本) - zousandian/NeteaseCloudMusicApi GitHub Wiki

0. 状态

该第二版 API 为早期版本,返回结构和第一版类似,交互地址有个特征 weapi

目前网页端采用的是新的第二版 API 详见 网易云音乐API分析(v2)新

1. 总览

所有交互需要指定一个参数:

key value
referer http://music.163.com

下面列出的所有参数均为明文参数,实际提交需要进行加密,详见最下方第 7 条目

2. 搜索

POST http://music.163.com/weapi/search/pc

新方案 POST http://music.163.com/weapi/cloudsearch/get/web?csrf_token= 返回的结构不一样

明文参数

s: 关键字

limit: 返回数据条数限制

type: 搜索类型

  • 1 单曲
  • 10 专辑
  • 100 歌手
  • 1000 歌单
  • 1002 用户
  • 1009 电台
  • NULL 推荐(实测失效)

offset: 偏移数量,用于分页

csrf_token: 非关键操作,值可为空

3. 专辑

POST http://music.163.com/weapi/album/{album_id}

参数

album_id(GET): 专辑 ID

csrf_token: 非关键操作,值可为空

4. 歌单

POST http://music.163.com/weapi/playlist/detail

参数

id: 歌单 ID

csrf_token: 非关键操作,值可为空

5. 单曲

POST http://music.163.com/weapi/song/detail

参数

ids: 歌曲 ID,数组形式,可为多个

csrf_token: 非关键操作,值可为空

6. URL 分析

http://m10.music.126.net/[expTime]/[key1]/ymusic/[key2]/[key2]/[key3]/[MD5].mp3

参数

expTime: 过期时间戳,标识本 URL 只能在过期时间前请求,否则返回 403

key123: 我还没搞明白,求大神们指教

MD5: 该歌曲的 MD5 校验码,小写

7. 加密请求

实际请求的参数应当加密为如下形式:

params: Db3xGrqMzXwQqWAIe7AlqILYssUuypZlUS+6Dr8g//OEK8SPpjsZht2j+/C+UZ/rAh1bsXoFDEZxQUwdVB5oc1nSRRT3gpmRgzmwhR8yBCboIk+Uf8PvV2azs2WQ10zewCEps4IAIW1Dbes4jxGcEh7pmUXurQQcrjf9VjzOp64XwKuzunQbb0JG8CwybMIyHjWAC6osTnopHVkSikLsMggTLCVADoK3s+a75VWaYY0srdCoS3FJq8tvvnVGim6k3TpsFkiII/r5DCm8EyvfLrZcsiB5Kpx96ZjxZXYw4yU=

encSecKey: 8ac3219a491bfde0c81532fb4d6c8cd919cc2613631f11a82a72a1fd02e535eb5fbce3c51e7bb8ad2ba06590c391ddec326aa6d6535f36bcb2dd074cf9601f1e874fbfe96787f341d824ea8f03781713355e745472949cb44d8b4c6ed1c76944e2d684add7e746f8ab6119caa7270dd3373320345d946dfb2647c66ab6ef51e5

其中 params 为 AES 对称加密后的参数,encSecKey 为 RSA 加密后的 AES 密钥。 所有公钥等参数详见代码,这里不列出。

1. 生成 AES 密钥

AES 密钥需要随机,否则服务器会 dump 掉相同密钥的请求(数量多的话)。
密钥的形式为 16 位随机字母或数字,[0-9a-zA-Z]

function createSecretKey($length=16){
    $str='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $r='';
    for($i=0;$i<$length;$i++){
        $r.=$str[rand(0,strlen($str)-1)];
    }
    return $r;
}

2. AES 加密

在上述 API 中,首先将 POST 参数值拼写为如下形式,以搜索 hello 为例:

{"s":"hello","type":"1","limit":"1000","offset":"0","csrf_token":""}

然后对上面的明文进行两次 AES 加密,第一次密钥为 nonce,第二次为第一步生成的密钥。

$data['params']=$this->aes_encode($string,$this->_nonce);
$data['params']=$this->aes_encode($data['params'],$this->_secretKey);

AES 加密的具体算法为:AES-128-CBC,输出格式为 base64
AES 加密时需要指定 iv:0102030405060708

本步骤加密后的结果即为 params

3. RSA 加密

最后对第一步生成的 secretKey 进行 RSA 加密,不过这个加密非常诡异,不是标准的形式。下面引用 darknessomi 的结论

RSA 加密采用非常规填充方式,既不是 PKCS1 也不是 PKCS1_OAEP,网易的做法是直接向前补0
这样加密出来的密文有个特点:加密过程没有随机因素,明文多次加密后得到的密文是相同的
然而,我们常用的 RSA 加密模块均不支持此种加密,所以需要手写一段简单的 RSA 加密
加密过程 convertUtf8toHex(reversedText)^e%N
输入过程中需要对加密字符串进行 hex 格式转码

PHP 中需要使用大数运算类,如果嫌不够清真的话可以自己实现,需要使用快速幂算法加速

function rsa_encode($text){
    $rtext=strrev(utf8_encode($text));
    $keytext=$this->bchexdec($this->strToHex($rtext));
    $a=new Math_BigInteger($keytext);
    $b=new Math_BigInteger($this->bchexdec($this->_pubKey));
    $c=new Math_BigInteger($this->bchexdec($this->_modulus));
    $key=$a->modPow($b, $c)->toHex();
    return str_pad($key,256,'0',STR_PAD_LEFT);
}
function bchexdec($hex){
    $dec=0;
    $len=strlen($hex);
    for($i=0;$i<$len;$i++) {
        $dec=bcadd($dec,bcmul(strval(hexdec($hex[$i])),bcpow('16',strval($len-$i-1))));
    }
    return $dec;
}
function strToHex($str){
    $hex='';
    for($i=0;$i<strlen($str);$i++){
        $hex.=dechex(ord($str[$i]));
    }
    return $hex;
}

本步骤加密后的结果即为 encSecKey