Canvas指纹与WebGL指纹:为什么换了 IP 网站还能认出同一台电脑

博客 / IP 与隐私

Canvas指纹与WebGL指纹:为什么换了 IP 网站还能认出同一台电脑

换了 IP 还能被网站认出来,常见原因之一是 Canvas指纹和 WebGL 指纹。网页让浏览器画一段文字或一个 3D 场景,再把画出来的像素读回去算哈希。像素结果由显卡、驱动、操作系统和字体渲染方式决定,和 IP 无关,所以换线路、开无痕窗口、清 Cookie 之后,这个值通常不变。

本文先用两段能直接粘进浏览器控制台的代码,让你亲眼看到自己的渲染哈希和显卡型号;再解释它为什么这么稳定;最后对比几种主流浏览器内置防护的思路和代价。结果可以和 cdnping 的 IP 与环境检测 里的“硬件指纹”一项对照,这部分在本地计算,不上传。

Canvas 指纹到底是怎么算出来的?

Canvas 是浏览器的 2D 绘图接口。网页可以在一块不显示出来的画布上写字、画渐变、叠 emoji,然后调用 toDataURL() 或 getImageData() 把像素读出来。

同样一句“Hello, 你好”,在不同机器上画出来的像素并不完全一致。差异来自:

  • 字体回退:系统里有没有指定字体,缺了用哪个替代。
  • 抗锯齿和次像素渲染:Windows、macOS、Linux 的文字平滑策略不同。
  • GPU 和驱动:开启硬件加速时,部分绘制交给显卡,不同显卡和驱动版本的浮点误差、混色方式略有差别。
  • emoji 字库:各系统自带的 emoji 图形完全不同。

肉眼看不出这些差别,但哈希值会完全不同。网站并不关心图像本身,只关心这个哈希。

自己动手:在控制台看你的 Canvas 哈希

打开任意网页,按 F12(macOS 上是 Cmd+Option+I)进入控制台,粘贴下面这段代码回车。它只在本地运行,不发送任何数据。

(async () => {
  const c = document.createElement('canvas');
  c.width = 280; c.height = 60;
  const ctx = c.getContext('2d');
  ctx.textBaseline = 'top';
  ctx.font = '16px Arial';
  ctx.fillStyle = '#f60';
  ctx.fillRect(100, 1, 62, 20);
  ctx.fillStyle = '#069';
  ctx.fillText('Hello, 你好 🌏 1.0', 2, 15);
  ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
  ctx.fillText('Hello, 你好 🌏 1.0', 4, 17);
  const data = new TextEncoder().encode(c.toDataURL());
  const buf = await crypto.subtle.digest('SHA-256', data);
  const hex = [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
  console.log('Canvas 哈希:', hex.slice(0, 16));
})();

输出是一串 16 位的十六进制字符。记下它,后面验证稳定性时要用。

WebGL 指纹能读出我的显卡型号吗?

能,而且往往是原样读出。WebGL 是浏览器里的 3D 接口,它有一个调试扩展 WEBGL_debug_renderer_info,可以返回显卡厂商和型号字符串。继续在控制台粘贴:

(() => {
  const gl = document.createElement('canvas').getContext('webgl');
  if (!gl) return console.log('WebGL 不可用');
  const ext = gl.getExtension('WEBGL_debug_renderer_info');
  console.log('厂商:', ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR));
  console.log('渲染器:', ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER));
  console.log('最大纹理尺寸:', gl.getParameter(gl.MAX_TEXTURE_SIZE));
  console.log('扩展数量:', gl.getSupportedExtensions().length);
})();

在 Windows 的 Chromium 系浏览器上,渲染器一栏通常是 ANGLE (厂商, 显卡型号, Direct3D11 ...) 这样的格式,显卡型号写得很清楚。Firefox 和 Safari 会对这个字符串做不同程度的概括,例如 Safari 上常见的只是“Apple GPU”。

除了型号字符串,WebGL 指纹还包括两部分:

  1. 参数和扩展列表:最大纹理尺寸、支持的着色器精度、扩展名单等几十项,组合起来就有区分度。
  2. 渲染哈希:和 Canvas 一样,让显卡画一个带光照的 3D 图形再读回像素。这一项直接反映 GPU 和驱动的计算差异,所以也常被叫作“显卡指纹”。

音频指纹又是什么?

原理相同,只是换成了声音。网页用 OfflineAudioContext 在内存里生成一段振荡波,经过压缩器处理后取样本求和,整个过程不播放任何声音。不同 CPU、系统音频栈和浏览器实现,在浮点运算上的细小差别会让结果的末几位小数不同。

音频指纹的区分度一般不如 Canvas 和 WebGL,但它和前两者互相独立,组合使用时能进一步缩小范围。

为什么换 IP、开无痕、重启都不变?

因为这些值不存储在任何地方,每次都是现场算出来的。只要“谁来画”没变,结果就不变。可以用下面的表判断哪些操作会让它变化:

你做的操作Canvas / WebGL 指纹是否变化原因
换 IP、开关代理不变渲染与网络无关
无痕窗口、清 Cookie不变指纹不依赖本地存储
重启浏览器、重启电脑不变硬件和驱动没变
更新显卡驱动可能变化渲染路径变了
浏览器大版本升级可能变化图形后端或字体渲染调整
开关硬件加速通常变化从 GPU 渲染切到软件渲染
新装或删除字体Canvas 可能变化字体回退结果不同
换一台同型号电脑可能相同同配置同系统的机器结果接近

最后一行说明,Canvas 指纹单独用并不能精确到“某一个人”。同一批采购、同一系统镜像的办公电脑,哈希很可能一样。所以网站一般会把它和 UA、屏幕、时区、字体等特征一起用,这部分在浏览器指纹是什么:网站不靠 Cookie 能认出你的 20 多项特征与自查方法里有完整拆解。

这也解释了标题里的问题:IP 只是网络层的身份,换 IP 改变的是“你从哪里来”,没有改变“你用什么设备”。关于 IP 本身能暴露什么,可以看你的 IP 地址到底暴露了什么。

一份可以照做的稳定性验证步骤

用前面两段代码,加上 cdnping 的检测页,按下面的顺序验证:

  1. 在普通窗口运行 Canvas 代码和 WebGL 代码,记下哈希和渲染器字符串。
  2. 打开同一浏览器的无痕窗口,重复一次。多数浏览器下两次结果一致。
  3. 完全退出浏览器再打开,重复一次。
  4. 在浏览器设置里关闭“使用硬件加速”,重启后再运行。哈希大概率变化,渲染器可能变成 SwiftShader 之类的软件渲染器。验证完记得改回来。
  5. 打开 /ip,看“硬件指纹”一项给出的显卡和渲染信息,和控制台结果对照。
  6. 如果用的是带指纹保护的浏览器,在同一站点刷新几次、或换一个站点运行,观察哈希是否每次不同。

第 6 步能直接告诉你,当前浏览器用的是哪一类防护。

浏览器内置的防护有哪几种思路?

主流做法可以归为三类,目标都是让网站拿不到稳定、独特的值,但代价不同。

阻止读取:Tor Browser

Tor Browser 在网页尝试读取 Canvas 像素时会拦下来,默认返回空白数据,并可能提示用户是否允许。WebGL 相关信息也被限制。效果最直接,代价是依赖读取 Canvas 的正常功能(例如在线图片编辑、截图分享、部分验证码)会失灵。

统一化:Firefox 的 resistFingerprinting

Firefox 在 about:config 中有一个 privacy.resistFingerprinting 选项。开启后,它的思路是让所有用户看起来一样:Canvas 读取需要授权,时区报成 UTC,部分语言和窗口尺寸也会被统一。这个选项最初来自 Tor Browser 合并回 Firefox 的补丁,Mozilla 并不建议普通用户长期打开,因为时区错乱会影响日历、网页时间显示,一些网站会表现异常。

Firefox 新版本另有一套范围更小的指纹保护,在增强型跟踪保护的“严格”模式和隐私窗口中生效,对 Canvas 读取加入少量噪声,副作用比 resistFingerprinting 小。

随机化:Brave 和 Safari

Brave 的 Shields 默认对 Canvas、WebGL 和音频读取加入轻微扰动,扰动按站点、按会话变化。结果是同一个网站在本次会话里拿到的值稳定,换一个网站或重开浏览器就变了,跨站把你串起来的难度因此上升。Safari 的高级指纹保护在无痕浏览中也采用了加噪的方式。

三种思路对照如下:

思路代表网站看到什么主要代价
阻止读取Tor Browser空白或固定的占位值依赖 Canvas 的功能失效
统一化Firefox resistFingerprinting和其他同类用户一样的值时区、语言被改,部分网站异常
随机化Brave、Safari 无痕、Firefox 严格模式每站每会话不同的值少数检测会把“值在变”本身当成异常

选哪种防护比较合适?

先想清楚目的。如果你只是不想被广告网络跨站追踪,Brave 的默认设置或 Firefox 的严格模式已经够用,日常功能基本不受影响。如果你需要高强度匿名,Tor Browser 的统一化方案更彻底,因为“大家都一样”比“每次都不一样”更难被区分。

有几点经常被忽略:

还要说明的是,银行、支付、账号登录等场景用设备指纹做风控,是为了识别盗号和欺诈。本文讨论的是普通用户如何理解和减少被跨站追踪,不涉及绕过任何平台的风控或检测,使用相关服务时请遵守其服务条款和当地法规。

在 cdnping /ip 里怎么看硬件指纹结果?

/ip 里有“硬件指纹”和“时区语言字体”两项检测,和本文的控制台代码看的是同一类设备特征。指纹都在你的浏览器本地计算,不上传;生成带水印的结果图时可以选择隐藏 IP。

把控制台输出和检测页结果放在一起,可以这样判断:

  • 渲染器字符串里有具体显卡型号:说明当前浏览器没有屏蔽这项,区分度较高。
  • 渲染器显示为概括名称或软件渲染器:浏览器做了概括,或硬件加速未开启。
  • 刷新后 Canvas 结果变化:浏览器在做随机化。
  • Canvas 结果为空或读取失败:浏览器在阻止读取。

每一项检测的具体含义,可以配合浏览器环境检测怎么看:13 项检测逐项解读一起看;检测口径和数据处理方式写在数据方法页面。

相关文章

继续阅读