4、WebGL 指纹:藏在浏览器里的 GPU 名片
WebGL 指纹,说白了就是通过浏览器把显卡的信息“偷”出来。你可能会想,一个网页凭什么知道我用什么显卡?嗯,这其实是 WebGL 标准本身允许的——它需要暴露一些硬件信息才能正常工作。但问题来了,这些信息组合在一起,就成了一个非常稳定的设备标识。
我个人习惯把 WebGL 指纹比作设备的“DNA”。CPU 信息可能被虚拟化隐藏,操作系统可以伪装,但 GPU 的渲染管线、驱动版本、厂商字符串这些底层数据,很难被完全篡改。我在项目中遇到过不少案例,用户换了浏览器、清了 Cookie,但 WebGL 指纹纹丝不动——这就是它的价值所在。
4.1 WebGL 上下文:一切的基础
要获取 WebGL 指纹,第一步是创建 WebGL 上下文。说白了,就是让浏览器给你一个 3D 渲染的环境。代码很简单:
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
console.log('浏览器不支持 WebGL');
return;
}
这里有个坑——有些浏览器或设备会禁用 WebGL。比如某些企业安全策略、老旧手机、或者隐私模式下的特殊限制。我曾经在做一个金融项目时,发现大约 3% 的用户无法获取 WebGL 上下文。所以,一定要做降级处理,不能因为 WebGL 拿不到就整个指纹采集失败。
document.createElement 创建离屏 canvas,既不影响页面渲染,又能拿到上下文。
4.2 GPU 型号与驱动信息:核心指纹源
拿到上下文之后,就可以开始“盘问”显卡了。WebGL 提供了两个关键接口:
gl.getParameter(gl.RENDERER)— 返回渲染器字符串gl.getParameter(gl.VENDOR)— 返回厂商字符串
实际拿到的数据长这样:
// 渲染器示例
"ANGLE (NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0)"
"Apple M1 Pro"
"Intel Iris OpenGL Engine"
// 厂商示例
"Google Inc. (NVIDIA)"
"Apple Inc."
"Intel Inc."
你想想看,这些字符串里包含了多少信息?显卡型号、驱动版本、甚至底层渲染引擎。而且不同操作系统、不同浏览器返回的格式都不一样。比如 Windows 上 Chrome 会用 ANGLE 层把 OpenGL 转译成 Direct3D,所以字符串里会带 "Direct3D11" 字样。而 macOS 上 Safari 直接暴露原生 Metal 驱动的信息。
4.3 渲染器与厂商字符串:深度解析
光拿字符串还不够,我们需要解析它。为什么?因为同一个 GPU 型号,在不同浏览器、不同驱动版本下,返回的字符串可能略有差异。比如:
| 设备 | Chrome 返回 | Firefox 返回 |
|---|---|---|
| RTX 3080 (Win) | ANGLE (NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0) | NVIDIA GeForce RTX 3080/PCIe/SSE2 |
| M1 Pro (Mac) | Apple M1 Pro | Apple M1 Pro |
| Intel UHD 620 (Win) | ANGLE (Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0) | Intel(R) UHD Graphics 620 (WHL GT2) |
看到没?同一块 RTX 3080,Chrome 和 Firefox 返回的格式完全不同。所以我在做指纹归一化时,会写一套解析规则:
function normalizeRenderer(renderer) {
// 提取核心 GPU 型号
const patterns = [
/NVIDIA GeForce ([\w\s]+)/,
/AMD Radeon ([\w\s]+)/,
/Intel.*(UHD|Iris|HD Graphics) ([\w\s]+)/,
/Apple (M\d+ Pro?|M\d+ Max?)/
];
for (const pattern of patterns) {
const match = renderer.match(pattern);
if (match) return match[0];
}
// 兜底:返回原始字符串的哈希
return hashString(renderer);
}
4.4 WebGL 指纹的稳定性与隐私问题
WebGL 指纹的稳定性,可以说是所有浏览器指纹中最高的之一。为什么?
- 硬件绑定: GPU 是物理硬件,不会像软件那样频繁变动
- 驱动更新慢: 普通用户几年都不更新一次显卡驱动
- 跨浏览器一致: 同一台机器上,Chrome、Firefox、Edge 拿到的 GPU 信息基本一致
我做过一个长期跟踪实验:在 1000 台设备上,连续 6 个月采集 WebGL 指纹。结果发现,超过 95% 的设备指纹完全没变。剩下的 5% 主要是驱动更新或系统重装导致的。
但稳定性高,也意味着隐私风险大。你想想看,一个能稳定跟踪你半年的标识符,如果被滥用会怎样?
privacy.resistFingerprinting 选项,开启后会向网站报告假的 GPU 信息。
作为开发者,我们需要在业务需求和用户隐私之间找平衡。我个人建议:
- 明确告知用户: 在隐私政策里说明你会采集 WebGL 信息
- 只用于安全场景: 比如反欺诈、防薅羊毛,不要用于广告追踪
- 提供退出机制: 允许用户关闭 WebGL 指纹采集
- 不要存储原始数据: 存哈希值就够了,别把用户的显卡型号明文存数据库
4.5 知识体系总览
下面这张图,把 WebGL 指纹的整个链路串起来了:
4.6 实战避坑指南
最后,分享几个我在实际项目中踩过的坑:
- 虚拟机问题: 虚拟机里的 WebGL 指纹会暴露宿主机的 GPU 信息。比如你在 Mac 上跑 Windows 虚拟机,拿到的可能是 "Apple M1 Pro"。这其实是个好事——反而能帮你识别出虚拟机环境。
- 浏览器扩展干扰: 有些隐私扩展会篡改 WebGL 返回值。我遇到过返回 "Intel Software Renderer" 的情况,明显是被改过的。
- 无头浏览器: Puppeteer、Playwright 等工具默认使用 SwiftShader 软件渲染,返回的字符串和真实浏览器完全不同。这可以用来检测自动化工具。