关于某云音乐API接口加密算法的实现,网上已经有很多实现的例子,这里用dart
代码实现一遍,以某云音乐搜索接口为例,核心参数为:params
和encSecKey
。
{
's': keyword, // 搜索的关键字
'type': 1,// 搜索的类型
'limit': 30,//当前页的条数
'total': 'true',
'offset': 0 // 分页页码
}
其中params
参数是将以上参数通过aes-cbc-128
算法两次加密所得,这里用到插件Cipher2
,此插件暂时不支持mac,修改后做了个兼容。
static const String nonce = '0CoJUm6Qyw8W8jud';
static const String iv = "0102030405060708";
static const String randomString =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/// 参数 queryData:就是上面的json数据
static Future<Map<String, dynamic>> aesEncrypt(String queryData) async {
// 调用createSecretKey()返回一个16位的随机数
String key = createSecretKey();
// 兼容一下MAC上的加密
if (Device.isDesktop) {
String encryptedString = await Cipher2Mac.encryptAesCbc128Padding7ForMac(
queryData, NeteaseEncryptUtil.nonce, NeteaseEncryptUtil.iv);
String value = await Cipher2Mac.encryptAesCbc128Padding7ForMac(
encryptedString, key, NeteaseEncryptUtil.iv);
return {"params": value, 'encSecKey': rsaEncrypt(key)};
}
// 第一次加密,用到的 nonce 和 iv,这两个是固定的
String encryptedString = await Cipher2.encryptAesCbc128Padding7(
queryData, NeteaseEncryptUtil.nonce, NeteaseEncryptUtil.iv);
// 第二次加密,key:是上面生成的随机数,iv:是固定值
String value = await Cipher2.encryptAesCbc128Padding7(
encryptedString, key, NeteaseEncryptUtil.iv);
// 调用 rsaEncrypt 函数得到 encSecKey的值
return {"params": value, 'encSecKey': rsaEncrypt(key)};
}
// 对 randomString 随机返回一个16位的数
static String createSecretKey() => List.generate(
16,
(index) => NeteaseEncryptUtil.randomString[
Random().nextInt(NeteaseEncryptUtil.randomString.length)]).join();
encSecKey
参数则是将createSecretKey()
返回的随机数通过调用static rsaEncrypt(String text)
函数和返回得到,下面该函数的实现:
static const String pubKey = "010001";
static const String modulus =
"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7";
static rsaEncrypt(String text) {
// 字符串反转
text = reverse(text);
// 将16进制字符串转成 BigInt 类型
BigInt biText = BigInt.parse(strToHex(text), radix: 16);
// 将上面固定值 pubKey 转成 BigInt 类型
BigInt biEx = BigInt.parse(NeteaseEncryptUtil.pubKey, radix: 16);
// 将上面固定值 modulus 转成 BigInt 类型
BigInt biMod = BigInt.parse(NeteaseEncryptUtil.modulus, radix: 16);
// 幂次求模:biText 的 biEx 次幂,再对 biMod 求模。
// 如:a.modPow(3,5) 先进行逆运算再求模运算,对a进行3次方后除以5取余数。
BigInt biRet = biText.modPow(biEx, biMod);
// zFill函数作用是长度不满足256补0
return zFill(biRet.toRadixString(16));
}
// 转成16进制字符串
static strToHex(String text) {
String res = "";
for (int i = 0; i < text.length; i++) {
res = res + text.codeUnitAt(i).toRadixString(16);
}
return res;
}
// 长度不满足256补0
static String zFill(String str) {
while (str.length < 256) {
str = "0" + str;
}
return str;
}
接下来测试一下加密算法是否正确,
// 这个是抽取的工具类,为调用入口
NetUtil.search("旧梦一场", AudioSource.netease);
具体调用实现,这是为了方便展示,将limit
设置默认为1条数据。
Future<List<Map<String, dynamic>>?> search(String keyword,
{int limit = 1, int type = 1, int page = 0}) async {
RequestOptions options = RequestOptions(
path: "http://music.163.com/weapi/cloudsearch/pc", method: "post");
Map queryData = {
's': keyword,
'type': type,
'limit': limit,
'total': 'true',
'offset': page
};
// NeteaseEncryptUtil 加密算法实现的工具类
// 具体实现:https://github.com/joedrm/weapon/blob/master/lib/net/netease_encrypt_util.dart
options.queryParameters =
await NeteaseEncryptUtil.aesEncrypt(json.encode(queryData));
var result = await dio.fetch(options);
print(result.data);
}
成功返回的json数据
{"result":{"searchQcReminder":null,"songs":[{"name":"旧梦一场","id":1450163801,"pst":0,"t":0,"ar":[{"id":35264840,"name":"钟意","tns":[],"alias":[]}],"alia":[],"pop":100.0,"st":0,"rt":"","fee":8,"v":13,"crbt":null,"cf":"","al":{"id":89819948,"name":"旧梦一场","picUrl":"http://p4.music.126.net/IZBZ5GpCJFTShxMcJrKTzw==/109951165008690592.jpg","tns":[],"pic_str":"109951165008690592","pic":109951165008690592},"dt":174915,"h":{"br":320000,"fid":0,"size":6999405,"vd":-41240.0,"sr":48000},"m":{"br":192000,"fid":0,"size":4199661,"vd":-38649.0,"sr":48000},"l":{"br":128000,"fid":0,"size":2799789,"vd":-36967.0,"sr":48000},"sq":{"br":979427,"fid":0,"size":21414694,"vd":-41237.0,"sr":48000},"hr":{"br":1750048,"fid":0,"size":38263929,"vd":-41229.0,"sr":48000},"a":null,"cd":"01","no":1,"rtUrl":null,"ftype":0,"rtUrls":[],"djId":0,"copyright":0,"s_id":0,"mark":536879104,"originCoverType":1,"originSongSimpleData":null,"tagPicList":null,"resourceState":true,"version":13,"songJumpInfo":null,"entertainmentTags":null,"single":0,"noCopyrightRcmd":null,"rtype":0,"rurl":null,"mst":9,"cp":1416832,"mv":0,"publishTime":0,"privilege":{"id":1450163801,"fee":8,"payed":0,"st":0,"pl":128000,"dl":0,"sp":7,"cp":1,"subp":1,"cs":false,"maxbr":999000,"fl":128000,"toast":false,"flag":4,"preSell":false,"playMaxbr":999000,"downloadMaxbr":999000,"maxBrLevel":"hires","playMaxBrLevel":"hires","downloadMaxBrLevel":"hires","plLevel":"standard","dlLevel":"none","flLevel":"standard","rscl":null,"freeTrialPrivilege":{"resConsumable":false,"userConsumable":false,"listenType":null},"chargeInfoList":[{"rate":128000,"chargeUrl":null,"chargeMessage":null,"chargeType":0},{"rate":192000,"chargeUrl":null,"chargeMessage":null,"chargeType":1},{"rate":320000,"chargeUrl":null,"chargeMessage":null,"chargeType":1},{"rate":999000,"chargeUrl":null,"chargeMessage":null,"chargeType":1},{"rate":1999000,"chargeUrl":null,"chargeMessage":null,"chargeType":1}]}}],"songCount":300},"code":200}
界面上的搜索效果:
完整实现在这里github