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 指纹还包括两部分:
- 参数和扩展列表:最大纹理尺寸、支持的着色器精度、扩展名单等几十项,组合起来就有区分度。
- 渲染哈希:和 Canvas 一样,让显卡画一个带光照的 3D 图形再读回像素。这一项直接反映 GPU 和驱动的计算差异,所以也常被叫作“显卡指纹”。
音频指纹又是什么?
原理相同,只是换成了声音。网页用 OfflineAudioContext 在内存里生成一段振荡波,经过压缩器处理后取样本求和,整个过程不播放任何声音。不同 CPU、系统音频栈和浏览器实现,在浮点运算上的细小差别会让结果的末几位小数不同。
音频指纹的区分度一般不如 Canvas 和 WebGL,但它和前两者互相独立,组合使用时能进一步缩小范围。
为什么换 IP、开无痕、重启都不变?
因为这些值不存储在任何地方,每次都是现场算出来的。只要“谁来画”没变,结果就不变。可以用下面的表判断哪些操作会让它变化:
| 你做的操作 | Canvas / WebGL 指纹是否变化 | 原因 |
|---|---|---|
| 换 IP、开关代理 | 不变 | 渲染与网络无关 |
| 无痕窗口、清 Cookie | 不变 | 指纹不依赖本地存储 |
| 重启浏览器、重启电脑 | 不变 | 硬件和驱动没变 |
| 更新显卡驱动 | 可能变化 | 渲染路径变了 |
| 浏览器大版本升级 | 可能变化 | 图形后端或字体渲染调整 |
| 开关硬件加速 | 通常变化 | 从 GPU 渲染切到软件渲染 |
| 新装或删除字体 | Canvas 可能变化 | 字体回退结果不同 |
| 换一台同型号电脑 | 可能相同 | 同配置同系统的机器结果接近 |
最后一行说明,Canvas 指纹单独用并不能精确到“某一个人”。同一批采购、同一系统镜像的办公电脑,哈希很可能一样。所以网站一般会把它和 UA、屏幕、时区、字体等特征一起用,这部分在浏览器指纹是什么:网站不靠 Cookie 能认出你的 20 多项特征与自查方法里有完整拆解。
这也解释了标题里的问题:IP 只是网络层的身份,换 IP 改变的是“你从哪里来”,没有改变“你用什么设备”。关于 IP 本身能暴露什么,可以看你的 IP 地址到底暴露了什么。
一份可以照做的稳定性验证步骤
用前面两段代码,加上 cdnping 的检测页,按下面的顺序验证:
- 在普通窗口运行 Canvas 代码和 WebGL 代码,记下哈希和渲染器字符串。
- 打开同一浏览器的无痕窗口,重复一次。多数浏览器下两次结果一致。
- 完全退出浏览器再打开,重复一次。
- 在浏览器设置里关闭“使用硬件加速”,重启后再运行。哈希大概率变化,渲染器可能变成 SwiftShader 之类的软件渲染器。验证完记得改回来。
- 打开 /ip,看“硬件指纹”一项给出的显卡和渲染信息,和控制台结果对照。
- 如果用的是带指纹保护的浏览器,在同一站点刷新几次、或换一个站点运行,观察哈希是否每次不同。
第 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 的统一化方案更彻底,因为“大家都一样”比“每次都不一样”更难被区分。
有几点经常被忽略:
- 改得越多,越显眼。手动装多个修改指纹的扩展,往往会让 Canvas、WebGL、UA 之间互相矛盾。这种不一致本身就是很强的特征,浏览器指纹与环境检测:时区、语言、字体和 IP 对不上,各自暴露了什么讲的就是这个问题。
- 指纹保护不等于网络隐私。WebRTC 泄漏和 DNS 泄漏会直接暴露真实 IP,和渲染指纹是两回事,需要分别处理,可参考 WebRTC 泄漏怎么关 和 DNS 泄漏与 WebRTC 泄漏原理。
- 时区和语言同样是指纹。只处理 Canvas,时区和
Accept-Language照样能缩小范围,详见时区和语言也会暴露你。
还要说明的是,银行、支付、账号登录等场景用设备指纹做风控,是为了识别盗号和欺诈。本文讨论的是普通用户如何理解和减少被跨站追踪,不涉及绕过任何平台的风控或检测,使用相关服务时请遵守其服务条款和当地法规。
在 cdnping /ip 里怎么看硬件指纹结果?
/ip 里有“硬件指纹”和“时区语言字体”两项检测,和本文的控制台代码看的是同一类设备特征。指纹都在你的浏览器本地计算,不上传;生成带水印的结果图时可以选择隐藏 IP。
把控制台输出和检测页结果放在一起,可以这样判断:
- 渲染器字符串里有具体显卡型号:说明当前浏览器没有屏蔽这项,区分度较高。
- 渲染器显示为概括名称或软件渲染器:浏览器做了概括,或硬件加速未开启。
- 刷新后 Canvas 结果变化:浏览器在做随机化。
- Canvas 结果为空或读取失败:浏览器在阻止读取。
每一项检测的具体含义,可以配合浏览器环境检测怎么看:13 项检测逐项解读一起看;检测口径和数据处理方式写在数据方法页面。