前言
网易云的Vip音乐下载下来,格式不是mp3/flac这种通用的音乐格式,而是经过加密的ncm文件。只有用网易云的音乐App才能够打开。于是想到可不可以把.ncm文件转换成mp3或者flac文件,上google查了一下,发现有不少人已经做了这件事,但没有发现C语言版本的,就想着写一个纯C语言版本的ncm转mp3/flac。
NCM文件结构
ncm文件的结构,网上有人解析出来了,分为下面几个部分
信息 | 大小 | 说明 |
---|---|---|
Magic Header | 10 bytes | 文件头 |
Key Length | 4 bytes | AES128加密后的RC4密钥长度,字节是按小端排序。 |
Key Data | Key Length | 用AES128加密后的RC4密钥。 1. 先按字节对0x64进行异或。 2. AES解密,去除填充部分。 3. 去除最前面'neteasecloudmusic'17个字节,得到RC4密钥。 |
Music Info Length | 4 bytes | 音乐相关信息的长度,小端排序。 |
Music Info Data | Music Info Length | Json格式音乐信息数据。 1. 按字节对0x63进行异或。 2. 去除最前面22个字节。 3. Base64进行解码。 4. AES解密。 6. 去除前面6个字节得到Json数据。 |
CRC | 4 bytes | 跳过 |
Gap | 5 bytes | 跳过 |
Image Size | 4 bytes | 图片的大小 |
Image | Image Size | 图片数据 |
Music Data | - | 1. RC4-KSA生成S盒。 2. 用S盒解密(自定义的解密方法),不是RC4-PRGA解密。 |
两个AES对应密钥
unsigned char meta_key[] = { 0x23,0x31,0x34,0x6C,0x6A,0x6B,0x5F,0x21,0x5C,0x5D,0x26,0x30,0x55,0x3C,0x27,0x28 };
unsigned char core_key[] = { 0x68,0x7A,0x48,0x52,0x41,0x6D,0x73,0x6F,0x35,0x6B,0x49,0x6E,0x62,0x61,0x78,0x57 };
不得不佩服当初破解这个东西的人,不仅把文件结构摸得请清楚楚,还把密钥也搞到手,应该是个破解大神。有了上面的东西,剩下的就很简单了,按部就班来就行了。
一些算法准备
开始前我们需要把AES算法,BASE64算法,RC4算法和Json解析算法先写好。
除此之外还有一个编码问题,解析出来的ncm文件是用utf-8编码存储的,所以它在中文windows系统下汉字会出现乱码,因为中文windows系统采用的编码是GBK,两者不兼容,所以我们要写一个编码转换算法,将utf8格式字符串转位GBK的。Linux下不用转换,Linux本身就是用UTF-8的。
C语言没有这些库,都要自己来。
- AES用GitHub上的
tiny-AES-c - JSON用GitHub上的CJSON
cJSON - Base64和RC4算法比较简单我们自己写
unsigned char* base64_decode(unsigned char* code,int len,int * actLen)
{
//根据base64表,以字符找到对应的十进制数据
int table[] = { 0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,
22,23,24,25,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
long str_len;
unsigned char* res;
int i, j;
//计算解码后的字符串长度
//判断编码后的字符串后是否有=
if (strstr(code, "=="))
str_len = len / 4 * 3 - 2;
else if (strstr(code, "="))
str_len = len / 4 * 3 - 1;
else
str_len = len / 4 * 3;
*actLen = str_len;
res = malloc(sizeof(unsigned char) * str_len + 1);
res[str_len] = '\0';
//以4个字符为一位进行解码
for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
{
res[j] = ((unsigned char)table[code[i]]) << 2 | (((unsigned char)table[code[i + 1]]) >> 4);
res[j + 1] = (((unsigned char)table[code[i + 1]]) << 4) | (((unsigned char)table[code[i + 2]]) >> 2);
res[j + 2] = (((unsigned char)table[code[i + 2]]) << 6) | ((unsigned char)table[code[i + 3]]);
}
return res;
}
- RC4生成S盒
//用key生成S盒
/*
* s: s盒
* key: 密钥
* len: 密钥长度
*/
void rc4Init(unsigned char* s, const unsigned char* key, int len)
{
int i = 0, j = 0;
unsigned char T[256] = { 0 };
for (i = 0; i < 256; i++)
{
s[i] = i;
T[i] = key[i % len];
}
for (i = 0; i < 256; i++)
{
j = (j + s[i] + T[i]) % 256;
unsigned tmp = s[i];
s[i]=s[j];
s[j]=tmp;
}
}
//针对NCM文件的解密
//异或关系
/*
* s: s盒
* data: 要加密或者解密的数据
* len: data的长度
*/
void rc4PRGA(unsigned char* s, unsigned char* data, int len)
{
int i = 0;
int j = 0;
int k = 0;
int idx = 0;
for (idx = 0; idx < len; idx++)
{
i = (idx + 1) % 256;
j = (i + s[i]) % 256;
k= (s[i] + s[j]) % 256;
data[idx]^=s[k]; //异或
}
}
- Windows下utf8转GBK
#ifdef WIN32
#include<Windows.h>
//返回转换好的字符串指针
unsigned char* utf8ToGbk(unsigned char*src,int len)
{
wcha