指纹

Canvas 指纹:工作原理与防护方法

了解 HTML5 Canvas 指纹如何通过独特的渲染模式追踪用户,以及引擎级别的防护技术。

文档中心

想直接看维护中的产品文档?

这篇文章对应的主题已经有文档中心页面。需要规范流程、当前参数和长期参考时,优先看 docs。

什么是 Canvas 指纹?

HTML5 Canvas 元素最初用于在浏览器中绘制图形、渲染图表和创建交互式可视化。它是最广泛使用的 Web API 之一,驱动从数据仪表盘到浏览器游戏的各种功能。

然而,Canvas 有一个副作用:当浏览器在 Canvas 元素上渲染文本或图形时,精确的像素级输出取决于设备的 GPU、图形驱动、操作系统合成、字体渲染引擎和抗锯齿实现。这意味着两台不同的计算机渲染相同的 Canvas 指令会产生略微不同的像素数据。

这种差异在同一设备上是一致且可重复的,使其成为识别浏览器的稳定信号。根据普林斯顿大学 Web Transparency and Accountability Project 的研究,早在 2014 年,Canvas 指纹就在前 100,000 个网站中超过 5% 的网站上被发现,且其普及率此后持续增长。2020 年发表在 USENIX Security Symposium 的研究表明,当与其他浏览器信号结合时,基于 Canvas 的指纹区分设备的准确率超过 99%。

令人担忧的是,Canvas 指纹无需任何权限,不在设备上存储数据,且对用户完全不可见。没有提示,没有 cookie 横幅,浏览器中也没有内置的退出机制。

跟踪方运行的采集流程很短、很安静、可在百万级页面上重用。下图把"跟踪脚本进入页面"到"稳定标识符落到 device-graph 数据库"之间发生的事情拆解清楚。

How a tracker turns Canvas into a stable device ID 1. Tracker script loads Bundled with ad / analytics SDK 2. Draw test pattern fillText, fillRect, gradients 3. Read pixels back toDataURL · getImageData 4. Hash pixel buffer SHA / MurmurHash / xxHash 5. POST to collector Beacon · navigator.sendBeacon 6. Match in device graph Cross-site identity link No prompt · no cookie · invisible to the user The full pipeline runs in under 50 ms inside any iframe. Clearing cookies, switching IP, or opening incognito does not change the hash.

采集环节对跟踪方而言成本极低,对访客而言完全不透明。真正昂贵的部分在下游 device graph:每个挂同一段跟踪脚本的站点都会贡献哈希,被聚类到一起,构建出一份跟着用户穿越无关域名的画像。所以防御者要做的不是阻止某次绘制调用,而是让渲染输出稳定、真实、与浏览器其它信号一致

为什么 Canvas 输出因设备而异

要理解 Canvas 为何产生独特输出,需要了解渲染管线:

  1. 字体光栅化:不同操作系统使用不同的文本渲染引擎(Windows 上的 DirectWrite、macOS 上的 Core Text、Linux 上的 FreeType)。每个引擎产生微妙不同的字形形状、提示和抗锯齿模式。

  2. GPU 渲染:显卡及其驱动影响图形、渐变和合成操作的处理方式。NVIDIA GPU 产生的亚像素输出与 AMD 或 Intel GPU 略有不同,即使对于相同的绘图指令。

  3. 色彩管理:操作系统应用不同的色彩配置文件和伽马校正。在经过显示器校准的 macOS 系统上渲染的 Canvas 渐变与默认 Windows 安装上的相同渐变看起来不同。

  4. 抗锯齿算法:平滑边缘的方法因平台、GPU 驱动版本和操作系统设置而异。这些差异小到人眼不可见,但当像素数据转换为字符串时足以产生不同的哈希值。

  5. 浮点精度:不同的 CPU 架构可能略有不同地处理浮点数学运算,影响贝塞尔曲线渲染和变换计算。

当像素输出转换为数据 URL 或 ImageData 数组并进行哈希时,结果是一个稳定的、设备特定的标识符。此哈希在浏览器会话之间不变,在清除缓存后存留,并在隐私浏览窗口中持续存在。

下面这张图把三大主流宿主平台并列对比。每一列展示参与最终像素 buffer 的组件链,即使 JavaScript 绘制调用完全相同,每条链产生的 hash 都不一样

Same draw calls, three platforms, three hashes Windows host DirectWrite font engine ClearType + GDI hinting DirectX / ANGLE compositing Color profile + gamma Pixel hash A 2c6d03169685 macOS host Core Text font engine CoreGraphics smoothing Metal / IOSurface compositing ColorSync + Display P3 Pixel hash B 9c18bdc53952 Linux host FreeType font engine subpixel hinting · LCD filter ANGLE GL · Mesa compositor sRGB profile + display gamma Pixel hash C 4f72a18b6d4e Three different hashes from one identical drawCanvasTest() function. Trackers exploit exactly this divergence.

为什么常见隐私工具不够用

几种方法试图解决 Canvas 指纹问题,但每种都有显著的局限性:

VPN 和代理服务器

VPN 改变你的 IP 地址,但对 Canvas 输出没有任何影响。Canvas 指纹完全在浏览器的渲染引擎内运行,与网络流量无关。同一 VPN 后面的两台设备仍然产生完全不同的 Canvas 指纹。

隐私浏览/无痕模式

隐私浏览模式在会话结束时清除 cookie、历史和本地存储。它们不会以任何方式修改 Canvas 渲染管线。你在无痕模式下的 Canvas 指纹与正常窗口中的指纹完全相同。

浏览器扩展

阻止或修改 Canvas 访问的扩展面临一个根本性挑战:它们在 JavaScript API 层面运作,而非渲染层面。常见方法包括:

  • 完全阻止 Canvas:这很容易被检测,因为网站可以检查 Canvas 操作是否返回空或错误结果。被阻止的 Canvas 本身就是一个独特的指纹信号。
  • 添加随机噪声:向 Canvas 输出注入随机像素在每次页面加载时改变指纹。虽然这防止了稳定追踪,但产生了新问题:每次都变化的指纹本身就是浏览器使用隐私工具的强烈信号。
  • 返回虚假数据:用预计算数据替换 Canvas 输出会破坏依赖 Canvas 进行合法用途(图表、验证码、地图)的网站,且通常产生与浏览器其他信号不匹配的输出。

核心问题是扩展可以拦截 API 调用但无法控制实际渲染管线。这意味着它们所做的任何修改本质上都可以通过浏览器报告的内容与实际渲染的内容之间的一致性检查来检测。

浏览器级别的随机化

一些浏览器实现了 Canvas 随机化(如 Brave 的 Canvas 噪声功能)。这在每个来源上为 Canvas 输出添加小的随机扰动。虽然比基于扩展的方法更好,但随机化仍有权衡:

  • 噪声模式本身可以通过统计属性分析,这些属性与真实设备差异不同
  • 随机化输出可能与浏览器的 GPU、字体和操作系统信号不一致
  • 某些实现只随机化特定 Canvas 操作,留下其他操作作为标识符

BotBrowser 的引擎级别方案

BotBrowser 采取了根本不同的方法。它不是在 API 层面阻止、随机化或拦截 Canvas,而是在浏览器引擎级别控制渲染输出,产生与完整设备配置文件匹配的一致、真实的结果。

下图把四种常见防御模式按"现代检测脚本能看到什么"的视角并列对比。每一层上面的方案都因为"插入痕迹"暴露自己。引擎级别渲染根本不存在插入层

Defense layers and what detection scripts see 1. Block Canvas API toDataURL throws or returns empty. Trackers see absence as a strong signal: very few real browsers do this. 2. Inject random noise per page load Hash changes on every reload. A hash that changes every visit is itself a fingerprint of a privacy tool, not stability. 3. Wrap CanvasRenderingContext2D in JS shim prototype.toString, descriptor checks, and toDataURL caller stack trace all expose the wrapper. Detectable. 4. Engine-level rendering control (BotBrowser) Pixels are produced inside the rendering pipeline that matches the loaded profile. No JS hook to detect. Hash is stable across runs, consistent with GPU strings, fonts, OS metadata, and audio surfaces. Higher-numbered layers leave fewer detectable artifacts. Only layer 4 produces output indistinguishable from a real device.

工作原理

当你加载指纹配置文件时,BotBrowser 配置整个渲染管线以匹配该配置文件的设备特征。Canvas 输出不是在渲染后修改的。相反,渲染本身产生与目标设备一致的输出:

chrome --bot-profile="/path/to/profile.enc" \
       --user-data-dir="$(mktemp -d)"

这意味着:

  • Canvas 哈希在会话之间是稳定的,就像在真实设备上一样
  • 输出与配置文件的 GPU、操作系统和字体信号一致
  • 为合法目的渲染 Canvas 的网站(图表、地图、游戏)正常工作
  • 没有可被检测的 API 级别钩子或拦截

确定性噪声控制

对于需要跨测试运行可重复结果的用例,BotBrowser 支持噪声种子:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-noise-seed=12345

相同的配置文件和种子始终产生相同的 Canvas 输出,即使跨系统重启和不同的主机操作系统。这对以下场景很有价值:

  • 需要比较 Canvas 输出的回归测试
  • 需要可重复条件的研究场景
  • 验证指纹一致性的 CI/CD 管线

跨平台一致性

BotBrowser 在 Canvas 保护方面最重要的能力之一是跨平台一致性。在 macOS 或 Linux 上加载的 Windows 配置文件产生的 Canvas 输出匹配配置文件中定义的 Windows 渲染特征,而不是主机系统的渲染管线。

这只有在引擎级别控制渲染时才可能。扩展或 API 级别的方法无法覆盖操作系统之间的基本渲染差异。

Canvas 取证记录

对于需要审计 Canvas 行为的隐私研究人员和开发者,BotBrowser 可以记录所有 Canvas 操作:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-canvas-record-file="/path/to/canvas-log.json"

这会捕获每个 Canvas API 调用、其参数和结果输出,提供完整的审计线索供分析,无需任何代码注入。

配置指南

使用 Playwright 的基本保护

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/path/to/profile.enc',
    ],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  await page.goto('https://example.com');
  // Canvas output will be consistent with the loaded profile
  await browser.close();
})();

使用 Puppeteer 的确定性模式

const puppeteer = require('puppeteer-core');

const browser = await puppeteer.launch({
  executablePath: '/path/to/botbrowser/chrome',
  args: [
    '--bot-profile=/path/to/profile.enc',
    '--bot-noise-seed=42',
  ],
  headless: true,
  defaultViewport: null,
});

与其他保护措施结合

Canvas 只是众多信号之一。对于全面保护,将 Canvas 配置与时区、区域设置和代理设置结合:

chrome --bot-profile="/path/to/profile.enc" \
       --bot-noise-seed=12345 \
       --proxy-server="socks5://user:pass@proxy:1080" \
       --bot-config-timezone="America/New_York" \
       --bot-config-locale="en-US" \
       --bot-config-languages="en-US,en"

验证

启动 BotBrowser 后,验证你的 Canvas 保护是否正常工作:

const page = await context.newPage();

// Render Canvas content and capture the hash
const hash1 = await page.evaluate(() => {
  const c = document.createElement('canvas');
  c.width = 200;
  c.height = 50;
  const ctx = c.getContext('2d');
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillStyle = '#333';
  ctx.fillText('BotBrowser Canvas test', 2, 2);
  ctx.fillStyle = 'rgba(0, 50, 255, 0.5)';
  ctx.fillRect(50, 10, 100, 30);
  return c.toDataURL();
});

// Reload and render again
await page.reload();
const hash2 = await page.evaluate(() => {
  const c = document.createElement('canvas');
  c.width = 200;
  c.height = 50;
  const ctx = c.getContext('2d');
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillStyle = '#333';
  ctx.fillText('BotBrowser Canvas test', 2, 2);
  ctx.fillStyle = 'rgba(0, 50, 255, 0.5)';
  ctx.fillRect(50, 10, 100, 30);
  return c.toDataURL();
});

console.log('Canvas output stable:', hash1 === hash2);

你也可以使用 CreepJS 或 BrowserLeaks 等公共指纹测试工具进行验证。这些工具应该报告一致的 Canvas 哈希且无异常。

检查内容:

  • Canvas 哈希在同一会话的页面重新加载中保持不变
  • 使用相同配置文件和噪声种子时,Canvas 哈希在会话之间匹配
  • 不同的配置文件产生不同的 Canvas 哈希
  • 指纹测试工具不显示与 Canvas 相关的警告

防御者用来确认 Canvas 保护是否生效的验证矩阵如下。每一行代表一个环境,每一列代表一个应当成立的属性。任何一格不达标的方案都会被有决心的指纹收集器检测到。

Cross-platform verification: same profile, identical hash Windows host running BotBrowser profile = mac_arm64.enc seed = 100 canvas hash 9c18bdc53952 webgl1: 0f9829ee244b webgl2: b879347569e8 UA: macOS · GPU: Apple macOS host running BotBrowser profile = mac_arm64.enc seed = 100 canvas hash 9c18bdc53952 webgl1: 0f9829ee244b webgl2: b879347569e8 UA: macOS · GPU: Apple Linux host (Xvfb) running BotBrowser profile = mac_arm64.enc seed = 100 canvas hash 9c18bdc53952 webgl1: 0f9829ee244b webgl2: b879347569e8 UA: macOS · GPU: Apple Same profile + same seed produces identical Canvas, WebGL1, and WebGL2 hashes regardless of host. That is the property no JavaScript shim can deliver.

最佳实践

  1. 始终使用完整的配置文件。 Canvas 保护在所有信号一致时效果最佳。加载配置文件确保 Canvas、WebGL、字体和其他信号都与相同的设备身份对齐。

  2. 测试时使用确定性模式。 --bot-noise-seed 标志确保可重复的结果,这对自动化测试和 CI 管线至关重要。

  3. 不要将修改 Canvas 的扩展与 BotBrowser 混用。 修改 Canvas 的浏览器扩展会与 BotBrowser 的引擎级控制冲突。BotBrowser 原生处理 Canvas 保护。

  4. 代理位置与配置文件匹配。 配置为美国 Windows 设备的配置文件应使用美国代理。Canvas 一致性加上不匹配的地理位置会削弱整体指纹的一致性。

  5. 记录 Canvas 操作以进行审计。 在开发期间使用 --bot-canvas-record-file 来验证目标网站进行了哪些 Canvas 调用,并确认 BotBrowser 正确处理了它们。

常见问题

Canvas 指纹在无痕模式下有效吗?

是的。无痕模式不改变浏览器的渲染管线。你的 Canvas 指纹在正常和无痕窗口中完全相同。

网站能否检测到 Canvas 正在被保护?

如果保护通过阻止或随机化 Canvas 来工作,那么可以 - Canvas 数据的不一致或缺失本身就是一个信号。BotBrowser 通过渲染引擎产生真实、一致的输出而不是拦截 API 调用来避免这个问题。

BotBrowser 的 Canvas 保护是否影响网站功能?

不会。因为 BotBrowser 控制渲染管线而不是阻止 API 调用,所有依赖 Canvas 的功能(图表、地图、游戏、验证码)都正常工作。

BotBrowser 能产生多少个独特的 Canvas 指纹?

每个指纹配置文件生成一个独特的 Canvas 哈希。BotBrowser 提供数百个配置文件,企业用户可以为特定设备配置生成自定义配置文件。

Canvas 保护在所有平台上都有效吗?

是的。BotBrowser 支持 Windows、macOS 和 Linux 作为主机操作系统,并且可以为任何受支持的目标平台模拟 Canvas 输出,而不受主机限制。

Canvas 保护对性能有什么影响?

极小。因为保护在渲染引擎内运行,而不是通过后处理或 API 拦截,Canvas 操作以接近原生的速度运行。

总结

Canvas 指纹是网络上最普遍和最难对抗的追踪技术之一。BotBrowser 通过在浏览器引擎级别控制渲染管线来从源头解决这个问题,产生与完整设备配置文件匹配的一致、真实的输出。结合 WebGL 保护音频指纹控制全面的配置文件管理,BotBrowser 提供一致、真实且适合日常使用的隐私保护。

#Canvas#浏览器指纹识别#Html5#Privacy#Tracking

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。