WebGL 像素哈希指纹识别通过 GLSL 着色器渲染一个不可见的测试场景, 然后用 gl.readPixels() 读取输出像素——利用的是每块 GPU 处理浮点运算时存在的微小差异, 这些差异会产生一个稳定的哈希值,可跨所有浏览器识别你的硬件, 清除 Cookie 或使用 VPN 都无法消除它。你现在就可以在 whatsmy.fyi 查看自己的 WebGL 像素哈希。
简要总结
一段 GLSL 着色器程序在隐藏的 WebGL Canvas 中被编译并在你的 GPU 上执行。 渲染后的像素值被读回并哈希为一个紧凑的标识符。 由于 GPU 着色器单元中的浮点舍入方式因制造商、架构和驱动版本而不同, 相同的着色器在不同硬件上会产生微妙差异的像素输出—— 即使两台机器使用相同的 GPU 型号但安装了不同的驱动程序也是如此。 这使得像素哈希比仅读取 GPU 渲染器字符串更精细, 后者只能揭示硬件型号,而无法反映驱动层面的具体行为。
什么是 WebGL 像素哈希指纹识别?
WebGL 像素哈希指纹识别是 WebGL 指纹识别 这一更广泛家族中的一种特定技术,它针对的是你的 GPU 的计算输出,而非其元数据。 通过 WEBGL_debug_renderer_info 读取 GPU 渲染器字符串可以告知追踪者你的 GPU 型号, 但无法区分两块运行不同驱动版本的相同 GPU。像素哈希弥补了这一盲区。
该技术之所以有效,是因为 WebGL API 让 JavaScript 能够直接访问 GPU 着色器的执行过程。 指纹脚本可以编写一个执行数学运算的 GLSL 片元着色器——三角函数、小数部分、伪随机噪声—— 并让 GPU 执行它。写入每个像素的颜色值取决于 GPU 的浮点单元如何在内部处理这些运算。 不同的 GPU 架构会以不同方式累积舍入误差,同一 GPU 上的不同驱动版本可能产生可测量的不同输出。
这与 Canvas 指纹识别 密切相关,后者同样读取像素输出——但 Canvas 指纹针对的是 2D 字体渲染和抗锯齿差异。 WebGL 像素哈希直接针对 GPU 的 3D 着色器运算,使其既具有更高的熵,又更难标准化。
WebGL 像素哈希指纹识别的工作原理
整个过程在 JavaScript 中运行,耗时不到 50 毫秒,对用户完全不可见。分为四个阶段。
第一阶段 — 创建隐藏的 WebGL Canvas
脚本创建一个离屏 canvas 元素——从不附加到文档——并获取 WebGL 渲染上下文。 Canvas 尺寸通常较小(256×128 或 512×256 像素)以保持执行速度。该 Canvas 从不显示。
第二阶段 — 编译旨在最大化差异的着色器
编写一个 GLSL 片元着色器,执行对浮点精度差异敏感的运算。 最有效的着色器结合了 sin()、cos()、fract()和大系数乘法——因为这些运算在不同 GPU 架构中以不同方式累积舍入误差。 着色器将每个像素的屏幕坐标通过这些数学函数映射,以产生颜色值。
一个常见的示例使用如下公式:
// 用于像素哈希指纹的 GLSL 片元着色器
precision highp float;
void main() {
float x = gl_FragCoord.x / 256.0;
float y = gl_FragCoord.y / 128.0;
// 这些 sin/fract 组合会放大 GPU 浮点差异
float r = fract(sin(x * 12.9898 + y * 78.233) * 43758.5453);
float g = fract(sin(x * 93.989 + y * 17.211) * 43421.631);
float b = fract(cos(x * 51.234 + y * 31.456) * 12345.678);
gl_FragColor = vec4(r, g, b, 1.0);
}大常数乘数(43758.5453、43421.631、12345.678)意味着中间结果中的微小浮点差异会被放大, 当 fract() 截断它们时,就会产生可见的颜色差异。 两块 GPU 在中间运算中即使精确到小数点后 14 位,最终仍会产生不同的像素颜色。
第三阶段 — 用 gl.readPixels() 读取像素
着色器执行后,脚本调用 gl.readPixels() 将渲染后的像素数据从 GPU 内存传回 JavaScript 的 Uint8Array。 这使脚本可以直接访问 Canvas 中每个像素的 RGBA 值——即 GPU 着色器计算的原始数值结果。
// JavaScript:编译着色器、渲染并提取像素
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 128;
const gl = canvas.getContext('webgl');
// 编译并链接顶点和片元着色器(简略版)
const program = compileAndLinkShaders(gl, vertexSrc, fragmentSrc);
gl.useProgram(program);
// 绘制全屏三角带
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
gl.STATIC_DRAW
);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// 从 GPU 内存读取像素数据
const pixels = new Uint8Array(256 * 128 * 4); // 宽 × 高 × RGBA
gl.readPixels(0, 0, 256, 128, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
// 对像素缓冲区哈希 → 稳定的 GPU 指纹
const hash = fnv1a(pixels); // 或 MurmurHash、xxHash 等第四阶段 — 对像素缓冲区进行哈希
完整的像素数组——256×128 Canvas 有 131,072 字节——被哈希为一个紧凑的值。 常见选择是 FNV-1a(快速、非加密)、MurmurHash3,或对采样像素字节的简单数字求和。 结果是一个简短的十六进制字符串,代表你的 GPU 着色器单元的精确浮点行为。 该哈希在浏览器重启、隐私浏览会话和设备重启后保持稳定, 仅在更新 GPU 驱动或更换物理硬件时才会改变。
这与读取 GPU 渲染器字符串有何不同?
两者都是 WebGL 指纹识别技术,但在硬件栈的不同层次上运行,捕获不同维度的唯一性。
| 属性 | GPU 渲染器字符串 | WebGL 像素哈希 |
|---|---|---|
| 读取内容 | GPU 型号名称 + 厂商字符串 | 着色器计算输出 |
| 使用的 API | WEBGL_debug_renderer_info | gl.readPixels() |
| 能区分相同 GPU、不同驱动? | 不能——相同 GPU 始终产生相同字符串 | 能——驱动差异会改变像素输出 |
| 被 privacy.resistFingerprinting 阻止? | 是——返回通用占位符 | 部分——需要额外的标准化处理 |
| 在虚拟机上有效? | 不一致——虚拟机通常返回 Mesa/SwiftShader | 有效——软件渲染器也会产生独特输出 |
| 需要 WebGL 扩展? | 需要——WEBGL_debug_renderer_info | 不需要——使用核心 WebGL |
像素哈希被认为是更稳健的信号,因为它不依赖于浏览器可以直接阻止的调试扩展。 只要 WebGL 渲染可用——超过 95% 的浏览器都支持—— 即使渲染器字符串返回通用值,也能提取出某种形式的像素级差异。
WebGL 像素哈希有多独特?
根据 Fingerprint.com 的研究和关于 GPU 指纹识别的学术研究, 像素哈希单独贡献约 5–7 位熵。这意味着可以将你的浏览器与大约 32–128 个相似配置区分开来—— 即使在与其他信号组合之前,这也是有意义的缩小范围。
| 发现 | 数值 | 来源 |
|---|---|---|
| 仅 WebGL 渲染输出的熵值 | 约 5.7 位 | Fingerprint.com 研究 |
| 像素哈希跨浏览器重启的稳定性 | 接近 100%(仅在更新驱动时改变) | Cao 等人,NDSS 2017 |
| 同一硬件上的跨浏览器一致性 | 高——硬件运算与浏览器无关 | Cao 等人,NDSS 2017 |
| 与 GPU 渲染器 + 厂商组合时的可识别性 | 共享相同三元组的访客不足 0.01% | Inria / 鲁汶大学实地研究 |
| WebGL 浏览器支持率(Chrome、Edge、Safari、Firefox) | 95–99% | BrowserLeaks WebGL 测试 |
与 GPU 渲染器字符串、厂商字符串和其他硬件信号组合后, 综合 WebGL 指纹可以将访客缩小到典型网站受众的不足 0.01%—— 这一精度超过了大多数第三方 Cookie 在跨站追踪中所能达到的水平。
为什么像素哈希能跨浏览器持续存在
这是让 WebGL 像素哈希在跨浏览器追踪方面特别有价值的特性—— 这是基于 Cookie 的系统从根本上无法解决的场景。 当你在同一台机器上使用 Chrome、Firefox 和 Edge 时, 每个浏览器都有完全独立的 Cookie 存储和本地存储。 但三个浏览器共享同一底层 GPU 和驱动程序。 GLSL 着色器在每个浏览器中独立编译,但在同一硬件上执行—— 产生的像素输出在所有浏览器中相同(或几乎相同)。
Cao 等人于 2017 年在 NDSS 发表的研究,通过这种方式展示了跨浏览器指纹识别能力: GPU 渲染输出被用于在同一设备上的不同浏览器安装之间关联浏览会话, 而这些浏览器之间没有任何共享状态。
谁在使用 WebGL 像素哈希指纹识别?
商业设备情报平台
Fingerprint.com、Threatmetrix 和 iovation 等平台在其设备标识符计算中, 将 WebGL 渲染哈希与数十个其他信号一起纳入。 对于欺诈检测来说,像素哈希充当稳定的硬件锚点—— 即使欺诈者清除了 Cookie、轮换了 IP 地址并切换了浏览器,GPU 渲染输出仍能将其会话关联在一起。
广告技术追踪
随着 Safari 和 Firefox 弃用第三方 Cookie,Chrome 也逐步淘汰, 广告网络越来越依赖硬件级指纹识别进行跨站用户识别。 WebGL 像素哈希特别有吸引力,因为它不需要任何浏览器存储,也不需要网络元数据—— 它完全自包含于 JavaScript 中。
无头浏览器和机器人检测
运行在服务器基础设施上的自动化浏览器通常使用软件渲染器, 如 SwiftShader(Chrome 无头模式)或 Mesa LLVMpipe(Linux)。 这些软件渲染器产生的 WebGL 像素哈希与真实 GPU 硬件产生的明显不同—— 而且运行相同软件渲染器版本的所有实例通常产生相同的哈希。 安全平台利用这种一致性来标记疑似机器人流量。
学术界的应对:UNIGL
针对 WebGL 像素哈希指纹识别最重要的学术防御是 UNIGL, 由 Wu、Li、Cao 和 Wang 在 2019 年 USENIX Security 论文 "Rendered Private: Making GLSL Execution Uniform to Prevent WebGL-based Browser Fingerprinting" 中提出。UNIGL 的工作原理是在 GLSL 着色器程序到达 GPU 之前在浏览器中重写它们, 将浮点运算转换为无论底层硬件如何都能产生统一输出的形式。
与简单地阻止 WebGL(Tor Browser)或添加噪声(Brave)的方法不同, UNIGL 旨在使渲染输出真正统一——在不破坏 WebGL 应用程序的情况下消除追踪信号。 研究人员证明,UNIGL 可以使不同 GPU 硬件的像素哈希相同, 对典型 Web 应用程序的性能开销不超过 3%。 然而,截至 2025 年,UNIGL 尚未被任何主流浏览器采用,仍是研究原型。
如何防范 WebGL 像素哈希指纹识别
有效防护需要阻止 WebGL 渲染访问、向像素输出注入噪声,或标准化计算过程。 以下方案按有效性从强到适合日常使用排列。
- Tor Browser(最强防护):Tor Browser 在其默认配置中完全禁用
gl.readPixels(),使脚本无法读取渲染的像素数据。 它还将 WebGL 限制在最低功能模式。代价是通过 Tor 网络浏览速度较慢, 以及部分 WebGL 应用程序无法使用。 - Brave 浏览器(日常使用推荐): Brave 的 Farbling 向 WebGL 像素输出注入少量随机化的每会话噪声值。 噪声在会话内保持一致(因此 WebGL 应用程序能正常工作), 但在会话之间和不同网站之间会改变——使像素哈希成为不可靠的追踪标识符, 同时不会破坏大多数 WebGL 内容。
- 启用
privacy.resistFingerprinting的 Firefox:在about:config中启用此标志可以标准化 Firefox 的 WebGL 输出, 使渲染哈希的区分度降低。防护力度不如 Brave 的 Farbling 激进, 但无需切换浏览器。部分 WebGL 应用程序可能出现异常。 - Apple Silicon 上的 Safari(部分防护):Safari 在 Apple M 系列芯片上会随机化 Canvas 输出并限制部分 WebGL 参数。 然而,底层 Apple GPU 架构仍可通过渲染输出被检测到指纹—— 尤其是在 M1、M2 和 M3 代之间的比较中,它们会产生可测量的不同着色器结果。
- 在浏览器设置中禁用 WebGL:在 Firefox 中, 在
about:config中将webgl.disabled设置为true会完全阻止 WebGL 渲染。这完全消除了像素哈希信号, 但也会破坏基于 WebGL 的地图(谷歌地图 3D、Mapbox)、浏览器游戏和部分视频会议界面。 - VPN 无法解决这个问题:VPN 加密你的网络流量并改变你可见的 IP 地址, 但对 GPU 着色器执行毫无影响。像素哈希在你的浏览器内计算, 作为 JavaScript 值发送到追踪服务器——而非作为网络元数据。 在 whatsmy.fyi 上验证 VPN 的 IP 层保护,但要明白它无法应对任何形式的指纹识别。
常见问题
WebGL 像素哈希和 WebGL 渲染器字符串有什么区别?
渲染器字符串是一个文本标签——例如 "NVIDIA GeForce RTX 4080/PCIe/SSE2"—— 由 WEBGL_debug_renderer_info 扩展返回。它识别你的 GPU 型号, 但对于所有拥有该精确 GPU 的机器都相同。 像素哈希是你的 GPU 实际计算的数值指纹——通过运行测试着色器并读取输出像素产生。 它捕获驱动层和硅片层的差异,使其在具有相同 GPU 型号的机器之间也能唯一标识。
像素哈希在无痕模式下有效吗?
有效。无痕模式阻止你的浏览器将 Cookie、历史记录和下载内容保存到磁盘—— 但不会改变你的 GPU 硬件或驱动程序。像素哈希完全由 GPU 计算生成, 在普通窗口和隐私窗口中完全相同。 只有具备主动指纹保护的浏览器(Brave、启用 privacy.resistFingerprinting 的 Firefox、 Tor Browser)才会产生不同的输出。
两块不同的 GPU 会产生相同的像素哈希吗?
会,但在统计上不常见。来自不同制造商(NVIDIA vs AMD vs Intel vs Apple)的 GPU 几乎从不产生相同的哈希,因为它们的着色器架构处理浮点运算的方式根本不同。 同一产品系列内的 GPU——例如两块运行相同驱动的 RTX 3070——通常会产生相同的哈希, 这就是为什么像素哈希与 GPU 渲染器字符串和其他信号组合时最为有效。
软件渲染器能用这种技术被指纹识别吗?
能。SwiftShader(Chrome 在硬件加速被禁用或不可用时使用)和 Mesa LLVMpipe 等软件渲染器会产生各自的像素哈希。这些软件渲染器的不同版本会产生不同的哈希。 禁用了硬件加速的 Chrome 会产生一个在会话间保持一致的 SwiftShader 哈希—— 且与任何真实 GPU 哈希明显不同,这就是为什么安全平台用它来标记无头浏览器环境。
更新 GPU 驱动会改变像素哈希吗?
可能会。驱动更新有时会改变着色器执行的浮点行为—— 特别是对于旧款 GPU 的三角函数。特定驱动更新是否改变像素哈希, 取决于更新是否修改了指纹着色器中使用的操作的着色器编译器或浮点单元行为。 实际上,重大驱动更新偶尔会改变哈希;次要稳定性更新通常不会。
广告拦截器能阻止 WebGL 像素哈希指纹识别吗?
只有在指纹脚本从广告拦截器过滤列表中列出的第三方域加载时才能阻止。 uBlock Origin 在中度或强力模式下会阻止商业追踪服务的大多数已知指纹脚本。 然而,从与网站相同域加载的第一方脚本不会被网络级广告拦截器阻止。 该技术本身(读取 WebGL 像素输出)是标准的浏览器功能,而非漏洞利用, 因此无法仅通过网络过滤来阻止。
这与"Rendered Private"论文中描述的技术相同吗?
是的。Wu、Li、Cao 和 Wang 的 2019 年 USENIX Security 论文 "Rendered Private: Making GLSL Execution Uniform to Prevent WebGL-based Browser Fingerprinting" 直接讨论了 WebGL 像素哈希指纹识别。 他们提出的缓解方案 UNIGL,在浏览器层面重写 GLSL 着色器源代码, 使渲染输出在不同硬件间统一。该论文仍是对这种攻击及潜在防御最为全面的学术处理。
相关文章
- 什么是 WebGL 指纹?你的 GPU 如何识别你的浏览器 — GPU 渲染器字符串和厂商如何暴露你的硬件型号
- 什么是 WebGL 厂商指纹识别? — GPU 制造商字符串如何缩小你的硬件系列范围
- 什么是 Canvas 指纹?网站如何在没有 Cookie 的情况下追踪你 — 捕获字体渲染和操作系统差异的 2D 兄弟技术
- 什么是音频指纹?AudioContext 如何追踪你的浏览器 — 你的设备音频堆栈如何产生唯一的浮点标识符
- 什么是浏览器指纹?网站如何在没有 Cookie 的情况下追踪你 — 所有指纹信号如何组合的完整指南


