Canvas 指纹噪声若处理不当会适得其反。了解朴素随机化为何可被检测,以及 Brave 的 farbling 如何规避陷阱。
Canvas 噪声——在浏览器返回像素数据时注入细微随机差异的做法——听起来是个优雅的解决方案。每次改变哈希值,追踪器就无法给你的设备钉上稳定的标识符。但在实践中,情况远比这复杂:设计粗糙的噪声只需两次 API 调用便能被检测出来,而一旦被检测到,你就会被归类为隐私工具用户。本文将解析噪声的工作原理、随机化为何会适得其反、结构化噪声与朴素随机性的本质区别,以及 Brave 的 farbling 机制如何规避了大多数陷阱——尽管它本身也留下了一些痕迹。
核心要点
- Canvas 噪声的目标是通过随机化追踪脚本读取的像素来破坏稳定指纹。
- 朴素的随机化只需两次 API 调用便可被检测:如果同一画布每次返回不同的像素,说明有东西在修改输出。
- 结构化的、按会话和来源生成的噪声(如 Brave 的 farbling)可以规避这一破绽,但仍然会暴露保护机制的存在。
- 趋同——让你的 Canvas 输出与大多数人保持一致——比随机化能提供更强的匿名性。
- 噪声大小至关重要:太大在统计上过于明显;太小则可能被多次采样平均消除。
Canvas 噪声如何工作
Canvas 指纹脚本绘制一个固定场景,然后通过 toDataURL() 或 getImageData() 读回像素。这些像素编码了你的 GPU、字体引擎和显卡驱动——这些硬件属性在页面加载之间保持不变。Canvas 噪声工具拦截读取操作,在数据到达脚本之前对其进行修改。
主要有两种修改方式:
拦截或置空。 返回空画布、全白结果或抛出错误。这能完全阻止指纹采集,但立刻就会暴露:空白结果本身就是一种指纹——每个使用此设置的访客看起来一模一样。一个简单的 data.every(b => b === 0) 检查就能发现它。
注入噪声。 保留真实像素但稍加扰动——例如在随机的 RGB 通道上加减 ±1 或 ±2——使哈希值改变,同时视觉效果保持可信。大多数隐私扩展(CanvasBlocker、JShelter)和 Brave 的 farbling 都采用这种方式。
关键的设计问题在于:噪声种子是如何生成的? 种子策略决定了噪声是否可被检测、是否可被还原。
为什么朴素的随机化可被检测
假设某个噪声工具在每次 API 调用时都生成全新的随机种子。对同一块未修改的画布调用 getImageData() 两次,得到的是两个不同的字节数组。而这在真实浏览器中永远不会发生——画布是一个位图,在两次读取之间没有任何绘制操作的情况下,结果应该完全相同。一个对 API 调用两次并比较输出的指纹脚本会立刻得出结论:噪声存在。
这不是理论上的攻击。Canvas 指纹库会检查这一点,因为它能可靠地区分被修改和未被修改的读取。即便脚本无法还原真实哈希,它现在知道你正在运行画布保护——而这个知识本身就是一种信号。在某些分类系统中,"确认为隐私工具用户"比稳定的哈希值更有价值,因为它对应的是一个特定的、更小的群体。
除了双次读取测试,朴素随机化还有第二个弱点:噪声的分布本身就是工具的指纹。如果你的噪声是每通道均匀 ±1、采用特定播种算法,返回像素的统计特性——均值偏移、方差、通道相关性——就能识别出具体的扩展及其版本。两个使用相同 CanvasBlocker 版本、相同设置的用户,其噪声分量在统计上是相似的,使他们与真实 GPU 差异区别开来,即使底层硬件完全不同。
结构化噪声:更聪明的替代方案
结构化噪声用满足三个属性的确定性种子替代每次调用的随机性:
- 会话内一致。 同一浏览器会话中对同一画布的两次读取返回相同的噪声字节。双次读取测试通过。
- 来源唯一。 在
bank.com上应用的噪声与在shop.com上不同,因此嵌入在两个站点的第三方追踪器无法跨站关联 Canvas 哈希。 - 跨会话不可预测。 新的浏览器会话使用新种子,切断跨会话追踪。
有了这个设计,双次读取检测就会失效。噪声稳定且内部一致,因此更难与真实的 GPU 级差异区分开来。跨站点和跨会话的关联都被切断了。
代价是结构化噪声仍然会暴露有东西在修改 Canvas 输出。将大量用户像素数组与参考值对比的对手可以发现与小型确定性偏移相对应的统计规律——但这需要针对每个受害者确切硬件配置的已知参考值,而远程服务器并不具备。
Brave 的 Farbling:工作原理
Brave 的 farbling 是结构化噪声的生产级实现,直接内置于浏览器而非以用户空间扩展的形式提供。这种位置至关重要:因为它在 JavaScript 层之下运行,任何脚本都无法通过检查原型链或计时 API 来区分 farbling 后的读取与真实浏览器行为。
种子设计实现了结构化噪声的三个属性:
- 安装密钥。 在安装时生成一个随机种子,安全存储在浏览器配置文件中。除非重置配置文件,否则它不会改变。
- 按来源派生。 安装密钥与当前站点 eTLD+1 的哈希组合,为每个网站生成不同的噪声配置。即便底层硬件不变,追踪器在
adnetwork.com上看到的 Canvas 哈希与在news.com上看到的不同。 - 每会话随机数。 在会话开始时混入一个新的随机值,在每次浏览器重启时轮换配置,防止跨会话关联。
像素扰动很小——通常是在随机选择的颜色通道上 ±1——使视觉输出与未修改的渲染无法区分。Brave 同时将 farbling 应用于 WebGL、音频和其他多个指纹采集面,因此 Canvas 噪声不会在一个看起来完全正常的信号集中突显出来。
Farbling 可以被还原吗?
还原原始 Canvas 哈希需要知道受害者确切硬件栈——GPU 型号、驱动版本、OS 字体引擎和浏览器版本——的预期像素值。远程服务器无法在没有物理访问相同硬件的情况下生成这个参考值。跨会话平均也不起作用:每会话随机数确保重复访问产生不同偏移,因此两次 farbling 读取的均值并不比任何单次读取更接近真实哈希。
然而,farbling 在种群层面并非不可检测。对大型指纹语料库的研究表明,Brave 用户的 Canvas 输出与普通 Chromium 输出的聚类方式不同——扰动模式是一个温和但真实的信号,表明使用的是 Brave 浏览器。你仍然可以被识别为Brave 用户——一个数千万而非数十亿的群体——但个人级别的追踪实际上已被打破。EFF 的 Cover Your Tracks 是一个公开工具,可以让你看到追踪器实际从你的浏览器观察到什么,包括你的 Canvas 输出是否看起来稳定或被随机化了。
趋同 vs. 随机化:更深层的权衡
Brave 的 farbling 是目前可用的最佳噪声实现之一,但它揭示了指纹防御中的一个结构性张力:随机化限制了追踪器能用你的指纹做什么,而趋同则将指纹作为信号完全消除。
Tor 浏览器采用不同的 Canvas 防御方式。它为每个用户返回一个标准化的近空白值,使所有 Tor 用户在追踪器面前看起来一模一样。没有噪声,没有哈希变化,没有统计特征——只有缺乏有意义的 Canvas 读取。追踪器无法得知你的渲染管线信息,你融入了 Tor 匿名集。代价是更受限制的浏览体验,以及你正在使用 Tor 的信号。
对于希望在保持正常浏览的同时阻断日常跨站广告追踪的用户,farbling 能很好地实现这一目标。对于需要在高风险场景中保持不可检测的用户,趋同是更强的姿态。在 Tor 会话中叠加 farbling 实际上可能适得其反,因为这会破坏 Tor 防御所依赖的趋同性。
Canvas 与 WebGL、音频和其他信号如何组合成综合指纹分数的完整图景,请参阅我们的 Canvas 指纹检测指南 和浏览器指纹完全解析。
常见问题
追踪脚本能否还原噪声并恢复我的真实 Canvas 哈希?
实际上不能。恢复真实哈希需要受害者确切硬件栈的参考渲染——GPU 型号、驱动版本、OS 字体引擎和浏览器版本。远程服务器无法生成这个参考值。跨会话平均在 Brave 的 farbling 下也无效,因为每会话随机数在每次重启时会改变偏移。
Canvas 噪声能防护所有指纹采集吗?
不能。Canvas 只是众多信号之一。即便 Canvas 噪声破坏了 Canvas 哈希,你的 WebGL 渲染器字符串、字体列表、屏幕分辨率和音频指纹仍然不变。能获取这些其他信号的追踪器通常仍然能大幅缩小你的匿名集。
CanvasBlocker 与 Brave 的 farbling 一样好吗?
两者都可以使用结构化噪声,但 Brave 的实现更难被检测和绕过:它在 JavaScript API 层之下运行,脚本无法通过检查原型链来区分它与真实浏览器行为。用户空间扩展在 JavaScript 原型层面打补丁,而老练的脚本可以探测这一层。启用了结构化噪声的 CanvasBlocker 比朴素的每次调用随机化或置空要好得多,但并不等同于浏览器级保护。
如何检查我的 Canvas 输出看起来是带噪声还是稳定的?
运行 BrowserInsight 的指纹检测工具。它会计算你的实时 Canvas 哈希,显示该值是否看起来稳定,并估算你的整体指纹相对于常见配置的唯一程度。


