视频加载失败

逆向网易云音乐 NCAE 音效格式

2241 字
11 分钟
逆向网易云音乐 NCAE 音效格式

背景#

网易云音乐 App 里有两百多个「音效」:超重低音、演唱会、迷幻电音这类场景音效,以及按耳机品牌型号适配的「适配耳机」。这些音效在客户端里不是明文下发的,而是一种私有格式 NCAE(NetEase Cloud Audio Effect)——加密压缩的二进制文件,App 内置解密逻辑后加载生效。

本文以 迷幻电音.ncae(125 字节)为例,把整条逆向链路拆开看:文件结构 → 密钥提取 → 流密码 → 解压 → 参数解读。用到的开源实现见文末参考。

先说结论:NCAE 解密后的明文有两种形态——

  • JSON 参数型:一段 JSON,声明 eq(均衡器)、bt(低音/高音)、rvb(混响)、se(空间音效)四个模块的开关和参数,场景音效基本都是这种;
  • WAV 卷积型:解密后直接是一个 WAV 脉冲响应(IR),用卷积套用,耳机适配类音效是这种。

迷幻电音属于前者。

文件结构#

迷幻电音.ncae 全文 125 字节,头部按大端序解读:

偏移长度含义
044E 43 41 45魔数,ASCII 即 NCAE
445F 00 00 00密文数据长度(大端),0x5F = 95 字节
88全零未使用
1610D密钥块长度 + 1,即 key0 共 13 字节
1713密钥块 key0
3095密文(zlib raw deflate 压缩后再流加密)

对照验证:4 + 4 + 8 + 1 + 13 + 95 = 125,和文件大小严丝合缝。

结构本身没什么花活,有意思的都在后面两步。

密钥提取#

key0 是 13 字节,但真正参与解密的 key 只有 12 字节,提取方式是把 key0 的第 5 个字节抽出来当异或掩码,剩下的字节逐个与它异或:

key = np.array(key0[:4] + key0[5:]) ^ key0[4]

注意切片是 key0[:4] + key0[5:]——去掉的正是下标 4(第 5 字节)。也就是说头部里存的密钥自带一层轻度混淆:单看 key0 猜不出实际密钥,必须知道「去掉第 5 字节再异或」这条规则。迷幻电音.ncae 提取出的 12 字节密钥实际是 [100, 75, 57, 80, 69, 124, 218, 113, 101, 98, 136, 82]

这层混淆挡不住有心人(逆向 App 就能拿到规则),但对格式分析者是一块绊脚石——你直接拿 key0 去跑标准解密,得到的是乱码。

流密码:一个不换牌的 RC4#

解密器是一个 RC4 变体。熟悉 RC4 的知道它分两阶段:

KSA(密钥调度)——这部分和标准 RC4 完全一致:初始化 0~255 的 S 盒,然后用密钥反复打乱:

table = np.arange(0x100, dtype=np.uint8)
byte = 0
for i in range(0x100):
byte = (table[i] + key[i % len(key)] + byte) & 0xFF
table[i], table[byte] = table[byte], table[i]

PRGA(密钥流生成)——这里和标准 RC4 不一样了。标准 RC4 每生成一个字节都要交换 S 盒里的两个元素,S 盒持续自演化;而这个实现完全不交换,S 盒经 KSA 打乱后就冻结,密钥流直接从静态表里双索引导出:

for j in range(len(data)):
byte = table[(j + 1) & 0xFF]
data[j] ^= table[(table[(byte + j + 1) & 0xFF] + byte) & 0xFF]

逐项解释关键变量:

  • j 是密文字节下标,直接充当序号;
  • byte = table[(j+1) & 0xFF] 取第一层索引值;
  • 密钥流字节为 table[(table[(byte+j+1) & 0xFF] + byte) & 0xFF]——用第一层的值再查一次表,双重索引;
  • 明文 = 密文 XOR 密钥流,逐字节异或。

「无置换」意味着这个变体的密钥流周期性和统计特性都比标准 RC4 更弱,安全性进一步下降——不过对音效参数这种低价值数据,设计目标本来也只是「格式不透明」,不是真正的安全防线。

解压#

流密码解出的 95 字节还不是明文,而是 zlib raw deflate 流(无 zlib 头、无 gzip 头的纯 deflate 数据),对应参数 -zlib.MAX_WBITS

zlib.decompress(data, -zlib.MAX_WBITS)

解压后得到 136 字节的 UTF-8 JSON。136 字节的明文被压到 95 字节密文,压缩率约 70%。

参数解读#

解密出的完整 JSON:

{
"eq": {
"on": true,
"eqs": [5, 6.09, 2.2, 0, -1.6, 1.5, 0, 1, 2.57, 3.77]
},
"bt": {
"on": true,
"bass": 3,
"treble": 2
},
"rvb": { "on": false },
"se": { "on": false }
}

四个模块,迷幻电音开了两个:EQ 和低音/高音,混响(rvb)与空间音效(se)关闭。

eq.eqs 是 10 段图形 EQ 的各段增益(dB),频点为标准倍频程分布:

频点31Hz62Hz125Hz250Hz500Hz1kHz2kHz4kHz8kHz16kHz
增益+5+6.09+2.20-1.6+1.50+1+2.57+3.77

这里有一点 NCAE 格式本身没有交代:Q 值在协议里没有定义,完全由客户端实现决定,一般取的是固定值 Q ≈ 1.41(即 √2)。这个数不是随便挑的——对图形 EQ 里常见的 peak(钟形)滤波器,Q 与带宽(octave,倍频程)近似满足

Q=2BW2BW1Q = \frac{\sqrt{2^{BW}}}{2^{BW} - 1}

代入 BW = 1(即每段控制 1 个倍频程,也正好对应上表里相邻频点之间的间隔)算出来 Q ≈ 1.414,正好是 √2。也就是说客户端选 √2 不是巧合,而是「让每一段 EQ 的作用范围大致覆盖一个倍频程、彼此邻段之间平滑衔接、既不过窄产生突兀的尖峰也不过宽互相严重重叠」这个工程目标下的标准解,10 段图形 EQ、吉他效果器等场景里 Q=1.41 是很常见的经验值/近似值。

一眼就是「V 型曲线加超低频强化」:62Hz 顶到 +6.09dB、31Hz +5dB 拉满低频下盘,8k/16kHz 分别 +2.57/+3.77 提亮高频,只在 500Hz 轻微 -1.6dB 收一下中低频的浑浊感——典型的电子乐调音取向。

实现上每段是一个 peak 滤波器(scipy SOS 级联),增益非零的段才参与滤波;此外还有一个防削波细节:preamp = -max(最大正增益, 0),迷幻电音最大增益是 6.09dB,所以整体先预放 -6.09dB,保证 EQ 抬升后不会爆音。

bt 是低架/高架两个 shelf 滤波器,数值直接就是 dB 增益:

  • bass: 3 → 120Hz 低架 +3dB(RBJ shelf,S=1)
  • treble: 2 → 8kHz 高架 +2dB

同样有 preamp = -max(bass, treble, 0) = -3dB 的防削波预放。也就是说迷幻电音的最终效果是「10 段 EQ 曲线 + 两端 shelf 再叠一层」,低频实际叠加到了 +9dB 上下的量级(62Hz 段 +6.09 与 120Hz 低架 +3 在频响上部分叠加)。

rvb 与 se 虽然在这例里关闭,参数结构也已被还原:rvb 有 er/il/ol/rl/rvb/tc 六组(早期反射、各级延迟、房间列表、衰减时间等),se 有 ambience/presence/sshapper/stereoizer 四组(氛围、现场感、瞬态塑形、立体声扩展)——开了这两个模块的音效(如「演唱会」「livehouse」)参数量会大得多。

复现#

脚本见 Github:

AllenHeartcore
/
audioeffect-ncm
Reverse engineered audio effect module for NetEase Cloud Music
GPL-3.0
Jupyter Notebook
Terminal window
python main.py 歌曲.flac -e bass # 用仓库自带预设
python main.py 歌曲.flac -e 迷幻电音 # 用自己解密出的预设(需重命名为 .ncae 放入 presets/)

-n 开启输出响度归一化,-o 指定输出路径。也可以直接用仓库里的 _decrypt_all.py 批量把所有 .ncae 解成可读的 JSON / WAV。

整条链路总结:魔数校验 → 头部取密钥块 → 去位异或提 key → RC4 变体(标准 KSA + 无置换 PRGA)流解密 → raw deflate 解压 → JSON 参数(或 WAV 脉冲响应)。加密强度不高,但「私有格式 + 客户端解密」这一层窗户纸,对于防止普通用户直接拿走参数文件来说,够用了。

相关资料#

文件说明
ncae_raw.7z原始 .ncae 文件集合,未经处理
ncae_processed.7z解密后的原始输出(JSON / WAV,未格式化)
ncae_processed_fmt.7z格式化后的 JSON(缩进美化,便于阅读)
ncae_processed_fmt_wav2flac.7zWAV 卷积型音效转 flac 后的版本

频域脉冲响应#

fresp_plot_efx
fresp_plot_efx
fresp_plot_device
fresp_plot_device

部分参数表#

EQBassTreble
genre_electronic迷幻电音[5, 6.09, 2.2, 0, -1.6, 1.5, 0, 1, 2.57, 3.77]32
genre_electronic+动感电音[4, 4, 3, 1, -1, -2, -1, 2, 4, 4]
genre_rock摇滚经典[-5.7, 0, 1.5, -3.81, -0.2, -1.1, 0.6, 1.5, 1.91, 1.91]
genre_hiphop嘻哈音效[5.6, 5.09, 2, 0.5, -2, 1.2, 3.79, 0.5, 1.5, 4]22
genre_acg纯净 ACG[4, 6.19, 1.5, -0.5, -0.8, 2, 5.19, 1, -1.5, 4]23
genre_folk民谣音效[0, 3, 0, -0.8, 1.5, 4.8, 5.8, 3.2, -0.5, 2]01
genre_classical婉约古风[4, 2, 0, -0.5, -1, 3.5, 4.5, 1, -1, 3]22
eq_bass极重低音[6, 4.5, 3.2, 2.09, 0, 0, 0, 0, 0, 0]30
eq_vocal清澈人声[-1.5, -1, 0, 1.79, 4, 3.79, 1.79, 0.5, -1, 1]
spatial_surr3D 环绕
spatial_stereo独享立体声
spatial_stereo_surr环绕立体声
spatial_stereo_crystal水晶立体声
scene_concert音乐厅
scene_church教堂混响
scene_live演唱会现场[5.59, 5, 3, 0, -1, 0, 3.09, 4.5, 4, 5]32
scene_live_surr狂嗨 LIVE[-6, -6.5, -3.7, -9.93, -12, -12, -12, -12, -12, -12]12-12
scene_livehouseLiveHouse 现场

只列了部分数据,SE(Presence / Stereoizer / SShaper / Ambience)、RVB(OL / ER / RVB / TC)等其余场景与全部参数详见:param_table.html

参考#

AllenHeartcore/audioeffect-ncm

版权声明#

本文仅用于技术研究与学习交流,禁止用于商业用途,也不鼓励绕开客户端限制批量获取或分发音效文件。文中涉及的密钥提取与解密方式仅为格式分析所需的最小复现,版权属于网易公司(NetEase, Inc.)。

逆向网易云音乐 NCAE 音效格式
https://cialo.site/posts/ncae-reverse/
作者
洛璃
发布于
2026-08-31
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
洛璃
初春的离去,晚樱的谢幕
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
38
分类
12
标签
166
总字数
160,464
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.16.6
文章许可
CC BY-NC-SA 4.0
文章目录