背景
網(wǎng)易云音樂(lè)下載的歌曲格式多為ncm,這種格式除了用網(wǎng)易云音樂(lè)App其他播放器無(wú)法播放。于是想尋求其格式轉(zhuǎn)換的工具,搜索了下確實(shí)有相關(guān)工具,不過(guò)很多山寨工具需要收費(fèi)。然后又試圖尋求其他方式,經(jīng)過(guò)一番調(diào)研發(fā)現(xiàn)這玩意其實(shí)早已經(jīng)被人扒光了到處裸奔(我估摸著這個(gè)事情就是千防萬(wàn)防,家賊難防。因?yàn)榛谀壳皀cm文件的加密方式,要根據(jù)其加密字節(jié)流去反推加密過(guò)程好像有點(diǎn)難,不知道搞破解的同學(xué)怎么看這個(gè)事情)。例如:《網(wǎng)易云VIP音樂(lè)NCM文件轉(zhuǎn)MP3,C語(yǔ)言版本》https://blog.csdn.net/y123456wydhckd/article/details/128368486;于是索性自己動(dòng)手搞個(gè)JAVA版的,然后順帶加上個(gè)重命名的功能。網(wǎng)易云音樂(lè)文件命名規(guī)則是:歌手名開(kāi)頭,這樣下載后默認(rèn)的排序就是按照歌手名,一直連著聽(tīng)某一個(gè)咖咖的歌誰(shuí)都會(huì)覺(jué)得無(wú)趣(我一般都是開(kāi)車的時(shí)候用U盤聽(tīng)歌),重命名的做法是:增加一個(gè)按文件名進(jìn)行murmur哈希的前綴值,從而起到一個(gè)哈希散開(kāi)的效果。
1. 網(wǎng)易云音樂(lè)文件加密概要
1.1 加密文件元數(shù)據(jù)定義
首先看下網(wǎng)易云音樂(lè)ncm文件元數(shù)據(jù)定義,其實(shí)通過(guò)NcmMetaData的定義及注釋,大家就應(yīng)該能大致知道如何去解密網(wǎng)易云音樂(lè)ncm文件了。
/**
* NcmMetaData
*
* @author chenx
*/
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NcmMetaData {
/**
* 頭信息:10 bytes
* 數(shù)據(jù)結(jié)構(gòu)不明(忽略)
*/
private byte[] magicHeader;
/**
* RC4密鑰長(zhǎng)度:4 bytes(字節(jié)小端排序)
*/
private int rc4KeyLength;
/**
* RC4密鑰:var bytes
* 1. 按字節(jié)對(duì)0x64異或。
* 2. AES解密,去除填充部分。
* 3. rc4Key硬編碼干擾鹽字符串(17字節(jié)):neteasecloudmusic
* 4. RC4密鑰: 剩余字節(jié)
*/
private Rc4Key rc4Key;
/**
* 音樂(lè)信息長(zhǎng)度:4 bytes(字節(jié)小端排序)
*/
private int musicInfoLength;
/**
* 音樂(lè)信息:var bytes
* 1. 按字節(jié)對(duì)0x63異或。
* 2. 硬編碼干擾鹽字符串(22字節(jié)):163 key(Don't modify):
* 3. Base64進(jìn)行解碼。
* 4. AES解密。
* 5. 硬編碼Json前綴干擾字符串(6字節(jié)):music:
* 6. Music對(duì)象Json字符串
*/
private CloudMusic cloudMusic;
/**
* CRC: 4 bytes
*/
private byte[] crc;
/**
* Gap: 5 bytes
*/
private byte[] gap;
/**
* 專輯圖片大小:4 bytes(字節(jié)小端排序)
*/
private int albumImageSize;
/**
* 專輯圖片:var bytes
*/
private byte[] albumImage;
/**
* 音頻數(shù)據(jù)
*/
private byte[] audioData;
}
1.2 加密概要及總結(jié)
- 加密概要
- 自定義了一個(gè)緊湊的數(shù)據(jù)結(jié)構(gòu),例如:10字節(jié)頭、4字節(jié)密鑰長(zhǎng)度等;
- 自定義數(shù)據(jù)結(jié)構(gòu)中的關(guān)鍵信息又進(jìn)行自定義加密及編碼,例如:音頻數(shù)據(jù)加密密鑰、音樂(lè)信息等;
- 自定義數(shù)據(jù)結(jié)構(gòu)中加入了一些無(wú)用硬編碼干擾鹽字符串,例如:neteasecloudmusic、163 key(Don’t modify):等;
- NCM格式的加密文件中除了音頻數(shù)據(jù)外還包含音樂(lè)附屬信息,例如:專輯,歌手,歌名,圖片等;
- 音頻數(shù)據(jù)的加密密鑰位于加密文件的自身之中,加密算法為性能相對(duì)AES較好的非標(biāo)RC4加密算法;
-
加密總結(jié)
從以上幾點(diǎn)可以看出其實(shí)網(wǎng)易云音樂(lè)NCM文件的加密其實(shí)基于都是一些經(jīng)典老套路:
- 通過(guò)自定義數(shù)據(jù)結(jié)構(gòu)的方式將密鑰置于加密文件自身之中,同時(shí)加入一些無(wú)關(guān)的鹽做為干擾(網(wǎng)易云音樂(lè)有點(diǎn)low,都是一些硬編碼的玩意);
- 出于加解密效率考慮,選擇了以異或運(yùn)算為主的替換類加密算法(可以把異或運(yùn)算理解為不進(jìn)位的二進(jìn)制加法,效率自然超高,對(duì)相同的東西進(jìn)行二次異或即可得到原始值,因此基于異或的解密只需再來(lái)一次即可);
- 對(duì)于密鑰的管理則選擇安全性較高的高級(jí)加密算法(網(wǎng)易云音樂(lè)選擇的AES,一般來(lái)說(shuō)對(duì)于密鑰的管理推薦使用非對(duì)稱的RSA或者ECC及其變種);
2. RC4加密算法
2.1 RC加密算法4簡(jiǎn)介
RC4 加密算法的核心思想是通過(guò)在初始狀態(tài)下生成一個(gè)偽隨機(jī)的字節(jié)流,然后將明文與這個(gè)字節(jié)流進(jìn)行異或運(yùn)算,從而得到密文。具體來(lái)說(shuō),RC4 算法包括兩個(gè)主要步驟:
1. 密鑰調(diào)度算法(Key Scheduling Algorithm,KSA):
- 使用初始狀態(tài)的 S-box(置換盒:Substitution Box)。
- S-box 是一個(gè)包含 0 到 255 的數(shù)字的數(shù)組,初始狀態(tài)下是有序的。
- 根據(jù)給定的密鑰,通過(guò)對(duì) S-box 的多次置換和交換來(lái)打亂其順序,生成一個(gè)混亂的 S-box。
2. 偽隨機(jī)數(shù)生成算法(Pseudo-Random Generation Algorithm,PRGA):
- 使用經(jīng)過(guò)打亂的 S-box。
- 利用 S-box 生成一個(gè)偽隨機(jī)的字節(jié)流,這個(gè)字節(jié)流被用作密鑰流。
- 將明文與密鑰流進(jìn)行異或運(yùn)算,得到密文。
2.2 RC加密算法4實(shí)現(xiàn)
在 Java 中,RC4 算法的實(shí)現(xiàn)通常并不包含在標(biāo)準(zhǔn)的 Java 加密庫(kù)中,一個(gè)常用的 Java 加密庫(kù)是 Bouncy Castle,Bouncy Castle 提供了豐富的密碼學(xué)算法支持,包括 RC4。如果要使用Bouncy Castle進(jìn)行標(biāo)準(zhǔn)RC4加解密,java11可以添加依賴(bouncycastle jdk版本太多了):
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.76</version>
</dependency>
需要補(bǔ)充說(shuō)明的是:網(wǎng)易云音樂(lè)對(duì)于音頻數(shù)據(jù)加密的RC4是一個(gè)非標(biāo)的RC4,標(biāo)準(zhǔn)RC4中S-box(置換盒)是一個(gè)字節(jié)的數(shù)組,因此這里只能自己實(shí)現(xiàn):
package cn.bossfriday.cloudmusic.converter.cipher;
/**
* RC4
* <p>
* RC4 加密算法的核心思想是通過(guò)在初始狀態(tài)下生成一個(gè)偽隨機(jī)的字節(jié)流,然后將明文與這個(gè)字節(jié)流進(jìn)行異或運(yùn)算,從而得到密文。
* 具體來(lái)說(shuō),RC4 算法包括兩個(gè)主要步驟:
* 1. 密鑰調(diào)度算法(Key Scheduling Algorithm,KSA):
* * 使用初始狀態(tài)的 S-box(置換盒: Substitution Box)。
* * S-box 是一個(gè)包含 0 到 255 的數(shù)字的數(shù)組,初始狀態(tài)下是有序的。
* * 根據(jù)給定的密鑰,通過(guò)對(duì) S-box 的多次置換和交換來(lái)打亂其順序,生成一個(gè)混亂的 S-box。
* <p>
* 2. 偽隨機(jī)數(shù)生成算法(Pseudo-Random Generation Algorithm,PRGA):
* * 使用經(jīng)過(guò)打亂的 S-box。
* * 利用 S-box 生成一個(gè)偽隨機(jī)的字節(jié)流,這個(gè)字節(jié)流被用作密鑰流。
* * 將明文與密鑰流進(jìn)行異或運(yùn)算,得到密文。
*
* @author chenx
*/
public class RC4 {
private final int[] sBox = new int[256];
/**
* RC4密鑰調(diào)度(RC4-KSA:Key Scheduling Algorithm)
*
* @param key
*/
public void keySchedule(byte[] key) {
int len = key.length;
for (int i = 0; i < 256; i++) {
this.sBox[i] = i;
}
int j = 0;
for (int i = 0; i < 256; i++) {
j = (j + this.sBox[i] + key[i % len]) & 0xff;
int swap = this.sBox[i];
this.sBox[i] = this.sBox[j];
this.sBox[j] = swap;
}
}
/**
* RC4偽隨機(jī)數(shù)生成(RC4-PRGA:Pseudo-Random Generation Algorithm)
*
* @param data
* @param length
*/
public void randomGenerate(byte[] data, int length) {
int i = 0;
int j = 0;
for (int k = 0; k < length; k++) {
i = (k + 1) & 0xff;
j = (this.sBox[i] + i) & 0xff;
data[k] ^= this.sBox[(this.sBox[i] + this.sBox[j]) & 0xff];
}
}
}
3. 源碼及運(yùn)行
3.1 源碼
https://github.com/bossfriday/bossfriday-cloudmusic-converter文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-832368.html
3.2 運(yùn)行
為了方便大家本地調(diào)試,在項(xiàng)目中的ncm目錄中已經(jīng)放了3個(gè)ncm歌曲,大家可以按照以下方式進(jìn)行本地調(diào)試:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-832368.html
- 運(yùn)行:Bootstrap的main方法;
- 輸入:1;
- 輸入:ncm文件源文件夾路徑、轉(zhuǎn)換后文件夾路徑;
到了這里,關(guān)于JAVA版網(wǎng)易云音樂(lè)格式轉(zhuǎn)換器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!