Canvas 指纹是浏览器指纹技术中最强大的追踪手段之一。了解它是如何工作的,以及如何降低被追踪的风险。
Canvas 指纹的原理,是让浏览器在一块不可见的 HTML5 画布上绘制文字与图形,再把渲染出的像素读回来,哈希成一段短小而稳定的标识符。由于每台设备在执行同一套绘制指令时,都会因为 GPU、显卡驱动、操作系统和字体引擎的差异而产生细微不同的结果,这段哈希便成了一个悄无声息、无需任何 Cookie 就能跨站跟随你的签名。本文会比基础介绍走得更深:像素究竟在哪里产生分歧、Canvas 到底贡献了多少唯一性、如何判断自己正在被指纹采集,以及为什么某些常见的防御反而会适得其反。
Canvas 指纹究竟如何工作
这项技术依赖 <canvas> 元素——它本是用 JavaScript 绘制图形的工具。追踪脚本根本不需要把画布显示在屏幕上,它完全存在于内存之中。
几乎所有实现的流程都高度一致:
- 创建一块离屏画布。 脚本创建一块很小的画布(常见宽度为 200 到 300 像素),但从不把它可见地挂到页面上。
- 绘制一个固定场景。 脚本渲染一段预先设定好的文字,常常混用不同字体、颜色、一两个表情符号以及相互重叠的矩形。表情符号和生僻 Unicode 之所以受青睐,是因为它们会迫使系统字体栈做出渲染决策。
- 把像素读回来。 脚本调用
toDataURL()把画布导出为 Base64 编码的 PNG,或用getImageData()读取原始的 RGBA 字节数组。 - 哈希结果。 这些字节被送入哈希函数(常见的有 FNV 或 MurmurHash),生成一段紧凑的指纹,例如
e3b0c44298fc1c14。
精妙之处在于:指令对所有人都一模一样,但输出却不同。两位运行完全相同脚本的访客可能得到不同的哈希;而同一位访客每次回访都会得到相同的哈希——这正是追踪者想要的。
这块画布从不显示。你看不到任何闪烁、方框或视觉提示。正是这种隐蔽性,让 Canvas 指纹成为静默追踪的常用手段。
为什么像素会因设备而异
如果绘制「Hello」本应是确定性的,那字节为什么会变化?答案是,你系统中的多个层级各自引入了细微而可复现的差异。
GPU 与显卡驱动
抗锯齿、次像素渲染以及曲线的填充方式,都有一部分交给 GPU 处理。英特尔集成显卡、苹果自研 GPU 与英伟达独立显卡,对边缘像素的取整方式各不相同。即便两台机器使用相同型号的 GPU,只要驱动版本不同,结果也可能产生分歧。
字体光栅化
最大的单一影响因素是文字渲染。操作系统的字体引擎——Windows 上的 DirectWrite、macOS 上的 Core Text、Linux 上的 FreeType——决定了如何把字形轮廓转成像素。字体微调(hinting)、伽马校正以及具体的抗锯齿算法,都会随平台和版本而变,因此字母「g」边缘那些灰色像素,往往携带了出乎意料的区分信息。
操作系统与已安装字体
请求的字体是否存在,决定了浏览器是否会替换成后备字体。同一个表情符号,在某个系统上可能渲染成扁平的轮廓,在另一个系统上则是全彩字形。系统的彩色表情集(苹果、Noto、Segoe)尤其会留下强烈的印记。
浏览器引擎与版本
Chromium、Firefox 与 WebKit 各有自己的 Canvas 实现。色彩管理以及 toDataURL() 编码 PNG 数据的方式,都可能随浏览器版本而变——这也是为什么浏览器一升级,你的 Canvas 哈希有时就跟着变了。
Canvas 到底贡献了多少唯一性
Canvas 对追踪者有价值,并不是因为它能单独识别你,而是因为它贡献了相当可观的一份熵——也就是衡量一个信号能把「你是谁」缩小到什么程度的指标。在对真实环境中指纹技术的研究里,Canvas 一再被列为被动网站可读取的单项信号中熵值最高的一档,与完整的已安装字体列表不相上下。
话虽如此,Canvas 很少能凭一己之力锁定一个人。许多人共享着常见配置——一台装着默认字体、用 Chrome 的标准 Windows 笔记本,会与数以百万计的人撞车。它真正的威力体现在组合之中:Canvas 加上 WebGL、屏幕参数、时区和语言,合起来便逼近一个近乎唯一的指纹。想全面了解这些信号如何叠加,可参阅我们的浏览器指纹完整指南。
| 属性 | Canvas 指纹 |
|---|---|
| 是否需要存储 | 不需要(无 Cookie、无 localStorage) |
| 清除缓存/Cookie 后是否仍有效 | 是 |
| 隐身/无痕模式下是否仍有效 | 通常是 |
| 用户是否可见 | 否 |
| 典型熵贡献 | 在单项被动信号中处于高位 |
| 清除数据能否破解 | 否 |
| 更换 IP 能否破解 | 否 |
网站如何使用 Canvas 指纹
Canvas 指纹具有双重用途,同一套机制既可用于侵犯隐私,也可用于保护安全:
- 跨站追踪。 嵌入在众多网站中的广告与分析网络读取 Canvas 哈希,即便用户清除了 Cookie,也能认出回访者,并据此构建行为画像。
- 反欺诈与账户安全。 银行和支付机构会标记那些 Canvas 指纹与已知设备突然不符的登录,帮助识别账户盗用。
- 机器人与滥用检测。 无头浏览器和自动化框架产生的 Canvas 输出往往要么过于一致,要么以某些破绽十足的方式渲染,因此指纹有助于把真人和脚本流量区分开——参阅 机器人检测技术,了解这在实践中是如何运作的。
- 限流与防滥用。 服务把指纹当作一个稳定的键,用来限制那些靠不断更换 Cookie 来规避配额的账户。
如何判断自己正在被 Canvas 指纹采集
你看不到那块画布,但可以留意伴随它出现的行为。
留意 API 调用
在一次指纹采集尝试中,JavaScript 会在一块从未被加入可见页面的画布上,先调用 getContext('2d'),接着是 fillText(),最后是 toDataURL() 或 getImageData()。浏览器开发者工具和某些隐私扩展能够把这些调用暴露出来。
一个极简的检测钩子
你可以自己给 Canvas API 加上探针,记录可疑的读取行为:
// 检测那些读取画布像素却什么都不显示的脚本
(function watchCanvasReads() {
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
const origGetImageData = CanvasRenderingContext2D.prototype.getImageData;
HTMLCanvasElement.prototype.toDataURL = function (...args) {
if (!document.body.contains(this)) {
console.warn('[canvas-fp] 在离屏画布上调用了 toDataURL()', this);
}
return origToDataURL.apply(this, args);
};
CanvasRenderingContext2D.prototype.getImageData = function (...args) {
console.warn('[canvas-fp] getImageData() 正在读回像素', this.canvas);
return origGetImageData.apply(this, args);
};
})();
如果你更想直接看到自己的指纹,而非阅读代码,最简单的办法是运行 BrowserInsight 的指纹检测工具。它会实时计算你的 Canvas 哈希,把 WebGL 与音频信号一并展示出来,并估算你整体指纹的唯一程度。
各种防御手段及其取舍
世上没有十全十美的防御,而且——这一点尤为关键——某些热门方案反而会让你更容易被识别。以下是主流方法的对比。
趋同(Tor 浏览器的思路)
Tor 浏览器的策略,是让每个用户看起来都一模一样。它默认禁用 Canvas 读回,并在允许之前弹出提示,于是脚本要么得到空白结果,要么得到一个被整个 Tor 群体共享的值。融入一个庞大而趋同的人群,是最稳健的防御——但代价是一种高度标准化、有时颇受限制的浏览体验。
随机化(Brave 的 farbling)
Brave 走的是另一条路,称为 farbling:它对 Canvas 读回按会话、按域名加入微小噪声,使你的指纹在每个站点、每个会话都不相同,从而有效切断跨站关联。其中微妙的风险在于——「被随机化」本身就是可被检测的——若随机化实现得过于粗糙,反而会增加熵,因为「一台 Canvas 带噪声的浏览器」本身就是一项区分特征。设计良好的 farbling 会把噪声控制得既小又合理,以避开这个陷阱。关于噪声设计如何决定随机化有效还是适得其反的深入分析,参阅 Canvas 噪声 vs 真实哈希:随机化为何适得其反。
拦截扩展(CanvasBlocker)
像 CanvasBlocker 这样的扩展,可以拦截读回、返回伪造值,或按域名随机化。它给了你精细的控制权,但同样的告诫依然成立:一个异常或过于激进的伪装会显得格格不入,配置不当的拦截器,可能比它想隐藏的 Canvas 还要醒目。
禁用 JavaScript
通过 NoScript 之类的工具关闭 JavaScript,能彻底击败 Canvas 指纹,因为该 API 根本无法运行。代价相当高昂——大多数现代网站离开它就会崩坏——因此这只适合对安全极度敏感的场景。
| 防御手段 | 工作原理 | 主要取舍 |
|---|---|---|
| Tor 浏览器趋同 | 让所有人看起来都一样 | 体验受限、高度标准化 |
| Brave farbling | 按会话加噪声以切断关联 | 随机化本身可能被检测 |
| CanvasBlocker | 拦截/伪造/随机化读回 | 配置不当会适得其反 |
| 禁用 JavaScript | API 根本不运行 | 大多数网站会崩坏 |
常见问题
清除 Cookie 能去掉我的 Canvas 指纹吗?
不能。Canvas 指纹不在你的设备上存储任何东西——它是从你的硬件和软件如何渲染图形中推导出标识符的。清除 Cookie、缓存或站点数据对它毫无影响。
隐身或无痕模式能阻止 Canvas 指纹吗?
通常不能。无痕窗口会隔离 Cookie 和历史记录,但你的 GPU、驱动和字体并未改变,因此 Canvas 哈希通常与普通窗口里的一样。无痕模式对付的是基于存储的追踪,而非指纹。
网站能看出我在用 Canvas 拦截器吗?
有时能。如果你的 Canvas 返回了一个明显伪造、空白或带噪声的值,老练的脚本就能推断出有保护正在生效。这也是为什么趋同(看起来和大家一样)往往比随机化(看起来刻意与众不同)更稳健。
Canvas 指纹和 WebGL 指纹是一回事吗?
二者相关但不相同。Canvas 读取的是文字与图形的 2D 渲染;WebGL 则通过 3D 渲染探测你的 GPU,并直接暴露厂商与渲染器字符串。在一个组合指纹中,二者相辅相成——我们的 WebGL 指纹深度解析 详细讲解了 3D 这一侧。


