DuckDuckGo 浏览器会给 Canvas 加噪声并固定部分硬件值,但不会让你和所有人一样。开源代码改了什么、没改什么。
很多人在 DuckDuckGo 浏览器里打开指纹检测页,只想知道一件事:我的指纹受保护了吗?简短的回答是:这款浏览器会削弱一部分指纹信号,但并不像 Tor Browser 那样试图让所有用户看起来完全一致。它的保护混合了两种策略,两者都能在开源代码中看到。
核心要点
- DuckDuckGo 浏览器的保护以页面脚本的形式注入,来自开源仓库 content-scope-scripts,开关由 privacy-configuration 中远程下发的配置控制。
- 策略一是加噪声:Canvas 读回的像素会被改动,改动依据站点和浏览会话生成,因此哈希随站点、随会话而变。
- 策略二是固定值:
hardwareConcurrency、deviceMemory、部分screen属性和 Battery API 对所有人返回相同的答案。 - 这些都不会让你“变得统一”。本文涉及的功能没有触碰浏览器引擎、User-Agent、WebGL 渲染器和字体。
- 行为可能因平台和站点而异,因为配置里有按功能的开关和按域名的例外列表。
两种策略,而非一种
指纹防御通常在两种思路间选择。统一性让许多用户返回相同的值,这个值就不再能识别任何人,Firefox 与 Tor 的 resistFingerprinting 就是这样;噪声则扰动输出,让追踪者拿不到稳定的值,噪声的利弊见这里。
DuckDuckGo 按功能逐项混用两者:原始值熵很高、本来就会变化的(Canvas 像素)用噪声;一个数字就能轻松归一的(核心数、内存、色深)用固定值。这是务实的折中,目标是减少跨站追踪又不至于弄坏太多网站,而不是匿名承诺。
代码改了什么
每个功能都是 content-scope-scripts 中 injected/src/features/ 下的一个小文件,并对应 privacy-configuration 中 features/ 下的一个 JSON 文件。
Canvas(fingerprinting-canvas.js)。脚本包装了 getImageData、toDataURL 和 toBlob。只有在 fillText、arc、渐变创建等绘图调用之后,画布才会被视为可疑。读回这类画布时,脚本把它复制到离屏画布并在那里修改像素,密钥由会话密钥与站点域名构成(对像素校验和计算 HMAC)。改动极小:对一部分像素翻转某个颜色通道的最低位,并跳过与邻居相同的像素。哈希变了,画面看起来不变。由于密钥在一次会话内稳定,同一画布读两次得到相同字节,避开了我们的 Canvas 噪声文章里讲的“二次读取”破绽。共享的 fingerprinting-canvas.json 还把 webGl 设为 "enabled",于是 shaderSource、drawArrays 等 WebGL 调用也会把画布标为可疑,用 toDataURL 导出的 WebGL 渲染图同样会被加噪。不过这只作用于渲染出的像素,GPU 厂商和渲染器字符串并不受影响。
音频(fingerprinting-audio.js)。代码会给 getChannelData、copyFromChannel 及分析器方法返回的采样值加上极小的偏移,同样由会话和域名决定。但在共享的 fingerprinting-audio.json 中,该功能的 state 在撰写时是 "disabled",所以别默认音频噪声已生效。这个信号暴露什么,见音频指纹。
硬件(fingerprinting-hardware.js)。navigator.keyboard 被设为 undefined,hardwareConcurrency 和 deviceMemory 返回配置里的数字。共享配置目前设为 4 和 8;由于是远程更新,请自行重读 JSON。背景见 hardwareConcurrency 与 deviceMemory。
屏幕(fingerprinting-screen-size.js)。availWidth 和 availHeight 被强制为整屏宽高,availTop、availLeft 为 0,colorDepth、pixelDepth 为 24,从而隐藏任务栏和程序坞的尺寸;多显示器下的 screenX/screenY 也会被归一。屏幕宽高本身仍会暴露,见屏幕分辨率指纹。
电池与存储。 fingerprinting-battery.js 报告“已充满且在充电”,并让事件失效;fingerprinting-temporary-storage.js 把 webkitTemporaryStorage.queryUsageAndQuota 报告的配额上限设为 4 GB,削弱对磁盘大小的推断。
诚实的局限
远程配置。 每个功能都受配置控制。Canvas 脚本甚至在 additionalEnabledCheck 设置未开启时直接返回,音频功能在共享文件里是禁用的。因此实际生效哪些保护,取决于平台版本和当前配置,而不只是代码。
按站点例外。 配置列出了因破坏网站而关闭某项保护的域名。仅 fingerprinting-hardware.json 就列了 15 个,例如 gamestop.com 的原因是“点击加入购物车会短暂转圈,商品却没有加入购物车”。在这些站点上,该保护是关闭的。
没有覆盖的信号。 上述文件都不改 User-Agent、WebGL 渲染器字符串或已安装字体列表,浏览器本身基于平台自带的 Web 引擎。这些信号仍会构成指纹。本文查看的文件里没有针对它们的功能,但我们没有审计整个项目,所以请理解为“这些功能未覆盖”,而不是“从未保护”。
固定值也可能显眼。 在 16 核桌面上报告 4 核,是脚本可以与其他信号交叉验证的值,这种不一致本身就可能成为信号,见指纹硬件不匹配。
横向对比
| 浏览器 | 主要思路 | 例子 |
|---|---|---|
| DuckDuckGo | Canvas 加噪声,少数属性固定值 | Canvas 位翻转、固定核心数 |
| Brave | 跨多个 API 的按会话按站点“farbling” | 见 Brave 隐私更新 |
| Firefox / Tor | 统一性(resistFingerprinting) | 时间精度取整、标准窗口尺寸 |
这是策略的比较,不是给谁打分。底层技术请先读浏览器指纹详解。另外澄清:DuckDuckGo 搜索引擎是另一个产品,它的隐私承诺针对搜索日志,而非浏览器指纹。
自己测一测
在 DuckDuckGo 浏览器里打开我们的指纹检测,记下 Canvas 哈希,然后在新会话里重新访问。被加噪的 Canvas 哈希应该跨会话变化,而核心数、设备内存之类的值保持固定。我们不会标注“检测到 DuckDuckGo 保护”:工具只展示原始值,结论由你自己得出。
常见问题
DuckDuckGo 浏览器能完全阻止指纹追踪吗?
不能。它会改动部分读回值并归一部分属性,但引擎、User-Agent、WebGL 字符串等信号不在本文所述功能范围内。
我的 Canvas 指纹在每个站点都一样吗?
如果 Canvas 保护已生效就不一样:噪声密钥包含站点域名,所以同一幅绘图在不同站点、不同会话会得到不同哈希。
DuckDuckGo 的保护在每台设备上都相同吗?
不一定。功能通过按平台远程下发的配置启用,行为可能因操作系统和应用版本而异。


