前段时间帮朋友下歌放在车上听..结果好多都是ncm格式,伤心 ,搜索了下发现基本上这格式解密有好多昂,可惜UI我都不太想要..决定抄一下,自己做一个。 这里先记录下核心代码,回头补充个UI 做个小程序。
ncm 是网易云音乐的音乐容器格式,并非是音乐格式,文件内带有meta cover audio 等信息,所以我们需要根据对应的加密算法,解密回来转成mp3 内容进行播放。
这个解密很多人都已经实现了,可惜要么是python
要么是c
要么是go
,有一个js实现的结果是浏览器的,当然浏览器跟nodejs
也没啥差别,不过写代码的意义就在于折腾嘛,准备做个nodejs
版本的,顺便再加个electron
的界面。
由于所有的ncm的密钥都是相同的,所以这里就直接写死就可以了,如果不同..那就没办法了,可能更新了或有了其他的处理。出现的话就只能等待大神给密钥了。
const filePath = 'd:/nodejs/toy/网易云NCM2MP3/2.ncm';
const fs = require('fs');
const crypto = require('crypto');
let content = fs.readFileSync(filePath);
(async function () {
let start = 0;
let buff = Buffer.from(content);
//1.读取8字节,获取 magic header
let temp = buff.slice(start, start+8);
start += 10;//空余 2 字节
let header = Buffer.from([0x43, 0x54, 0x45, 0x4E, 0x46, 0x44, 0x41, 0x4D]);
if (!header.equals(temp)) {
throw new Error('文件头已损坏');
}
//读取 32 位 4字节的密钥长度
let keyLength = buff.readUInt32LE(start);
start += 4;
//根据密钥长度读取密钥内容
temp = buff.slice(start, start + keyLength);
let cipherText = temp.map(t => {
return t ^ 0x64;
})
start += keyLength;
//解密的key
let key = Buffer.from('687a4852416d736f356b496e62617857', 'hex');
//aes-128-ecb 解密
let decipher = crypto.createDecipheriv('aes-128-ecb', key, '');
let decodeText = decipher.update(cipherText);
decodeText += decipher.final();
//得到 keyData
let keyData = decodeText.substr(17).trim();
let key2Len = keyData.length;
keyData = Buffer.from(keyData);
//ttt
//将keyData 做 RC4-KSA 算法解密
let keyBox = Buffer.alloc(256);
for (let i = 0; i < keyBox.length; i++){
keyBox[i] = i;
}
let j = 0;
for (let i = 0; i < 256; i++) {
j = (keyBox[i] + j + keyData[i % key2Len]) & 0xff;
[keyBox[i], keyBox[j]] = [keyBox[j], keyBox[i]];
}
//读取4字节获取meta 长度
let metaLen = buff.readUInt32LE(start);
start += 4;
//读取meta内容
let metaContent = buff.slice(start, start + metaLen);
metaContent = metaContent.map(t => t ^ 0x63)
//去掉 22位 163 key(Don't modify):
metaContent = metaContent.toString();
metaContent = metaContent.substr(22);
metaContent = Buffer.from(metaContent, 'base64');
//解密meta 内容
const metaKey = Buffer.from("2331346C6A6B5F215C5D2630553C2728",'hex');
let decipher2 = crypto.createDecipheriv('aes-128-ecb', metaKey, '');
let meta = decipher2.update(metaContent);
meta += decipher2.final('utf8');
meta = meta.substr(6);
meta = JSON.parse(meta);
start += metaLen;
//5字节空白
start += 5;
//读取 4字节 crc32校验码
let crc32 = buff.readUInt32LE(start);
start += 4;
//读取4字节图片大小
let imgLen = buff.readUInt32LE(start);
start += 4;
//写入图片数据
fs.writeFileSync('./1.jpg', buff.slice(start, start + imgLen))
start += imgLen;
let audioData = buff.slice(start);
let m = 0;
for (let i = 1; i < audioData.length + 1; i++){
m = i & 0xff;
audioData[i-1] ^= keyBox[(keyBox[m] + keyBox[(keyBox[m] + m) & 0xff]) & 0xff]
}
fs.writeFileSync('d:/nodejs/toy/网易云NCM2MP3/2.mp3', audioData);
})();
其实也就是照着大神的代码抄一遍... 还是等我补UI吧,尽量做个好看易用的..
转载请注明出处: https://chrunlee.cn/article/netnase-ncm-2-mp3.html