Canvas、WebGL 和音频指纹:硬件是怎么出卖你的
三种最常用的硬件相关指纹各自靠什么产生差异,为什么同一台电脑换个浏览器就变了,以及为什么给指纹加噪声的扩展往往让你更显眼。最后说说 WebGPU。
5 分钟阅读 · 发布于 2026年9月10日
让浏览器画一行字、渲染一个三角形、跑一段没人听得见的音频,然后把结果做个哈希。这三样东西不需要任何权限,用户毫无感知,却能在几百万台设备里把你的机器分到一个很小的桶里。原理其实不复杂,花几分钟就能弄明白。
Canvas:画出来的差异
Canvas 指纹的脚本大同小异:新建一个不显示的 <canvas>,用几种字体写一句话(常见的是 "Cwm fjordbank glyphs vext quiz" 之类含各种字形的句子,再加个 emoji),叠几个半透明的彩色矩形和圆弧,然后调 toDataURL() 拿到 PNG 的 base64,或者用 getImageData() 读出像素,再算哈希。
按理说同样的绘图指令应该得到同样的像素。实际上不会,因为最后落到像素的路径太长了:
- 字体光栅化。同一个字体文件,Windows 的 DirectWrite、macOS 的 Core Text、Linux 的 FreeType 把曲线变成像素的方式各不相同。
- 抗锯齿和次像素渲染。边缘那些半透明的过渡像素,颜色值取决于系统设置、屏幕方向、是否开了 ClearType 类的技术。
- GPU 与驱动。浏览器会把 canvas 绘制交给 GPU 加速,圆弧的插值、混合模式的舍入在不同显卡和驱动版本上有细微差别。
- 颜色管理。显示器的色彩配置文件有时会影响 canvas 输出。
单看一个像素的差别微不足道,但哈希会把所有差别放大成完全不同的字符串。好消息(对追踪者而言)是它很稳定:同一台机器、同一个浏览器,今天和下个月画出来的一模一样。这个稳定性正是它能当身份标识的前提。
WebGL:不只是画面,还有一堆参数
WebGL 指纹分两层。第一层跟 canvas 类似:渲染一个带渐变和顶点着色器的场景,读回像素做哈希。差异来源又多了一环——着色器编译器和 GPU 的浮点精度。
第二层更直接。WebGL 会老老实实报告一大堆静态信息:WEBGL_debug_renderer_info 扩展给出未脱敏的显卡厂商和渲染器字符串,比如 ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version) 或 ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11);还有支持的扩展列表、各种上限(最大纹理尺寸、最大顶点属性数、抗锯齿采样数)、着色器精度格式。这些值加起来的熵非常高——渲染器字符串一项就能把芯片型号、图形后端和大致驱动版本说清楚。
浏览器厂商也知道这一点。Safari 和 Firefox 在某些模式下会把渲染器字符串泛化成 "Apple GPU" 这类粗粒度名字。
音频:听不见的指纹
AudioContext 指纹最容易被误解为"要开麦克风"。不需要。脚本用 OfflineAudioContext 在内存里合成一段信号:一个振荡器产生三角波或正弦波,接一个动态压缩器(DynamicsCompressorNode),离线渲染几千个采样,把输出样本加总或哈希。整个过程无声无息,几十毫秒完成。
差异来自浮点运算的实现、浏览器用的 DSP 库、压缩器的具体算法、操作系统音频栈的舍入方式。同一份代码,Chrome 和 Firefox 算出的样本和会在小数点后几位不同,对哈希来说这就够了。
为什么同一台机器换个浏览器就变了
因为上面每一项的"最后一公里"都是浏览器自己实现的。Chrome 用 Skia 做 2D 绘图,WebGL 通过 ANGLE 转译到 Direct3D 11(Windows)或 Metal(macOS);Firefox 有自己的一套,Safari 又是另一套。文字排版引擎、音频后端也各不相同。所以 Canvas/WebGL/音频指纹识别的其实是"这台硬件 + 这个操作系统 + 这个浏览器版本"的组合,而不是硬件本身。
这也是为什么浏览器大版本升级后指纹经常会变——追踪者靠其他稳定信号(屏幕、时区、字体)把新旧指纹连起来。
加噪声的扩展为什么常常帮倒忙
市面上有不少"Canvas Defender"、"指纹保护"类扩展,思路是在 toDataURL 返回前给像素加一点随机扰动,让每次结果不同。听起来合理,问题有两个。
第一,真正的浏览器是稳定的。如果一个站点画两次 canvas 得到两个不同哈希,它立刻知道你在用扰动工具。本站的指纹真实性评分就有这一项:两次绘制不一致,扣分。用扰动工具的人相对少,这个信号本身就把你归入了一个小群体。
第二,如果扩展改成固定偏移(每次都一样),那么你的哈希在全世界只有你一个人拥有——比不加噪声时"和几千台同型号 MacBook 一样"更独特,正好和目的相反。
做得比较成熟的是浏览器自带的方案。Firefox 的 privacy.resistFingerprinting 会让 canvas 读取先弹权限、把 WebGL 参数统一成常见值,目标是让所有开了这个选项的用户看起来一样。Brave 的 "farbling" 思路更巧:用"站点 + 会话"作为种子做细微扰动,同一站点内你的指纹稳定(不会被识破用了扰动),跨站点则不同(无法关联)。
WebGPU 是下一个
WebGL 的继任者 WebGPU 已经在 Chrome 上可用,Safari 和 Firefox 截至写作时也在逐步跟进。它提供的 adapter 信息包括 vendor、architecture、device、description,以及一长串 limits(最大缓冲区大小、工作组尺寸等)和可选 features。这些比 WebGL 更贴近硬件本身,而且做计算着色器的性能测量也更容易,指纹研究已经把它当作新的信息源。本站的 WebGL 报告页会在浏览器支持时一并列出 WebGPU adapter 信息,你可以自己看看它暴露了多少。
常见问题
我能把 Canvas 指纹关掉吗? 可以,但代价是网页上很多依赖 canvas 的功能(图表、图片编辑、部分验证码)会坏掉,而且"关掉"这个行为本身可被检测。更实际的做法是用 Firefox 的 resistFingerprinting 或 Brave,接受"看起来像一群人"而不是"隐形"。
WebGL 渲染器显示 SwiftShader 或 llvmpipe 是什么意思? 说明浏览器在用 CPU 软件渲染,没有 GPU 加速。真人的桌面浏览器很少这样,虚拟机和无头浏览器则很常见,所以它是机器人检测里的一个信号。
在本站看到自己的 Canvas 哈希"和最近 N 次访问中的 M 次相同",M 大好还是小好? 大是好事,说明你和很多人一样,不容易被单独识别。M 等于 1 意味着这个值只有你有。