最近闲的没事让claude分析了一下雅马哈网站的水印,看起来除了明文MidStamp水印以外所有音符都加了频率非常低的频域水印。
以下是原始的分析报告
MIDI 水印分析
本版本去除了歌曲名称、发行商、文件名、哈希值、文件大小以及印章字符串和解码后的 ID。确切的数量均以百分比形式给出,因此无法用于识别特定产品。
样本
| 副本 | 来源 | 购买者 |
|---|---|---|
| A1 | 网站 A | 账号 1 |
| A2 | 网站 A | 账号 2 |
| B1 | 网站 B | 账号 3 |
| B2 | 网站 B | 账号 4 |
所有四个副本均采用相同的商业编曲:单轨道标准 MIDI 文件(格式为 Type 0,分辨率为 480 PPQ),包含数千个音符。它们的文件大小完全相同。
B1 和 B2 在字节级别上完全一致,尽管它们是用不同的账号购买的。
概要
两个网站使用的是同一个水印系统:MidStamp-1.00。该系统包含三个层级:
| 层级 | 网站 A | 网站 B |
|---|---|---|
| MidStamp 文本 ID | 按账号区分 | 两个账号相同(属于产品级或网站级) |
| 音符力度 ±1 位 | 按账号区分 | 一套固定数据(跨账号相同) |
| 时值抖动 (Duration jitter) | 网站层级 + 微小的账号层级 | 无网站 A 的抖动;其自身抖动未知 |
网站 A 对每次购买都进行个性化标记。网站 B 向所有购买者提供同一个带有印章的文件,因此其水印只能追溯泄露源头至网站 B,而无法追溯到特定账号。
副本之间没有其他差异。每个副本中的音符音高、通道和起始时间都完全相同,所有其他事件(CC、弯音、SysEx、特定于序列器的 Meta 事件)也完全一致。各个副本的大小也是相同的。
第 1 层:MidStamp 文本事件
在轨道开头的 Tick 0 处,存在一个文本 Meta 事件(FF 01):
MidStamp-1.00: <uuencode 编码的载荷>
载荷为一行 uuencode 编码文本。开头的 0 表示长度为 16 字节,末尾的 ` 字符为填充符。解码后可得到一个 128 位 ID。
A1、A2 和 B1/B2 中的 ID 各不相同。A1 和 A2 互不相同,而 B1 和 B2 共享同一个 ID。这些 ID 看起来像随机的 128 位数值,因此它们可能是哈希值或加密后的订单/用户 ID。在没有 MidStamp 密钥的情况下,无法还原原始 ID。
第 2 层:音符力度 ±1 位
音符通过(通道、音高、起始时间)进行一对一匹配。
相对于 B 的力度差异(占所有音符的比例):
| −1 | 0 | +1 | |
|---|---|---|---|
| A1 − B | 10.0% | 78.8% | 11.2% |
| A2 − B | 9.7% | 78.8% | 11.5% |
(A1 − B, A2 − B) 的联合分布:
| (A1−B, A2−B) | 占比 |
|---|---|
| (0, 0) | 68.4% |
| (−1, −1) | 5.7% |
| (−1, 0) | 4.2% |
| (0, −1) | 4.0% |
| (1, 1) | 5.1% |
| (1, 0) | 6.2% |
| (0, 1) | 6.5% |
| (−1, 1) / (1, −1) | 0 |
上述数据表明:
- 每个音符都是一个二进制插槽。 没有任何一个音符在这三个不同的副本中呈现出三种不同的力度,因此每个载体音符可以容纳两个相邻数值中的一个:即 1 个比特位。
- 约 42% 的音符携带比特信息。 约 32% 的音符在这三个副本之间存在差异。如果是随机比特,所有三个副本偶然达成一致的概率约为 ¼,这推算出载体音符的比例接近 42%。
- 各个副本是独立的印章标记。 每一对副本组合(A1/A2、A1/B、A2/B)在大约 21% 的音符上存在差异。这相当于载体音符的 50% 左右,正是独立的伪随机比特串所产生的特征。因此,网站 B 的文件是来自同一母带的另一个已加印副本,而不是未经修改的原版。
- 比特分布在整个文件中。 它们出现在每个通道中,并呈现出短片段分散分布的样式。
- 载荷具有高度冗余性。 对于一个 128 位的 ID 来说存在数千个比特,因此该方案大概率使用了重复编码或纠错码。这使得它能够抵抗局部修改、重新量化或截取片段。这些比特可能经过了密钥打乱,因此无法直接与文本 ID 进行匹配。
第 3 层:音符时值抖动
仅有音符结束时间(Note-off)发生了变动,音符起始时间从未改变。
| 对比项 | 时值差异比例 | 典型偏差值 |
|---|---|---|
| A1 vs A2(同一网站) | ~8% | 主要为 ±2 ticks |
| A1 vs B | ~49% | −2 … +3 ticks |
| A2 vs B | ~49% | −2 … +3 ticks |
时值模 10(Durations mod 10)占所有音符的比例:
| mod 10 | 0 | 1 | 2 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|
| A1 | 41% | 5% | 10% | 4% | 19% | 19% |
| B | 81% | <0.1% | <0.1% | 0% | <0.1% | 18% |
网站 B 的音符时值落在 10-tick 的网格上;而网站 A 的时值则偏离了网格。网站 A 的大部分抖动是两个副本所共享的(属于网站级或产品级),在此之上还叠加了一小部分按账号区分的抖动。在网站 A 内部,部分音符在不同副本间展现出了三种不同的时值,因此这一层不像力度层那样属于纯粹的二进制结构。
分析结论与影响
- 清除水印。 仅删除 MidStamp 文本是不够的。网站 A 的文件依然可以通过数千个力度比特以及按账号区分的时值抖动来识别买家。要完全清除水印,至少需要对每个音符力度进行重新随机化或重新量化,并将每个音符时值对齐到网格上。
- 无法察觉。 力度 ±1 以及时值 ±1–3 ticks 的微调在听觉上是无法察觉的。
- 网站 B 只能追溯至网站层级。 它的副本在不同账号之间是完全相同的,因此泄露只能追溯回网站 B,而无法追踪到具体的购买者。除非网站 B 根据账号以外的要素(例如时间或缓存周期)来生成印章,这种情况可以通过后续购买进一步验证。
分析方法
使用 Python 的 mido 库解析文件,并将 Note On/Off 事件匹配成组(起始时间、通道、音高、力度、时值)。随后对各个副本进行逐音符对比。非音符事件则作为(绝对 Tick,消息内容)的多重集进行对比。印章载荷使用 binascii.a2b_uu 进行解码。