一、改 UA 为什么对这件事没用先建立一个直觉UAUser-Agent是浏览器自己说的话而 WebGL 是浏览器替你报的硬件参数。前者是一段你随时能改的字符串后者直接来自 GPU 和驱动。所以「改了 UA 就换了身份」这个说法从根上就是错的。UA 只是指纹里权重很低的几十项之一而你真正没改动的是那块物理显卡。WebGL 能读到显卡靠的是一个扩展WEBGL_debug_renderer_info。它开放了两个参数参数返回什么UNMASKED_VENDOR_WEBGLGPU 厂商名UNMASKED_RENDERER_WEBGL完整 GPU 型号 渲染后端 shader 版本这两个参数和 UA 没有任何关系。你在 UA 里写iPhone也好、Pixel也好WebGL 那一栏读出来还是你的真实显卡。二、一条字符串里藏着多少信息拿 Windows 上 Chrome 读到的典型值举例ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Laptop GPU Direct3D11 vs_5_0 ps_5_0, D3D11)这一条字符串拆开是五层信息片段含义ANGLEWindows 上 Chrome 用的图形翻译层把 OpenGL ES 调用翻译成 Direct3DNVIDIA厂商GeForce RTX 3060 Laptop GPU具体型号 笔记本/桌面形态Direct3D11渲染 API 版本vs_5_0 ps_5_0顶点/片元着色器版本所以同一个显卡换不同的平台字符串会不一样——ANGLE前缀是 Windows 专属macOS 走 Metal 会变成类似ANGLE (Apple, ANGLE Metal Renderer: Apple M3 Pro, ...)Linux 走 Mesa 又是另一种格式。但无论怎么变它都能把一个真实的硬件型号报出来。型号 形态 渲染 API 着色器版本这已经能精确锁定到哪一代、哪个档次。三、关键参数对照表除了型号WebGL 还暴露一组能力参数。这些参数的重要之处在于即使型号字符串被屏蔽它们仍然能读。常用项如下参数说明示例量级VERSIONWebGL 版本字符串WebGL 1.0 / 2.0SHADING_LANGUAGE_VERSION着色语言版本WebGL GLSL ES 1.0MAX_TEXTURE_SIZE最大纹理尺寸16384主流独显MAX_VIEWPORT_DIMS最大视口尺寸16384×16384MAX_RENDERBUFFER_SIZE最大渲染缓冲16384MAX_VERTEX_ATTRIBS最大顶点属性数16MAX_VERTEX_TEXTURE_IMAGE_UNITS顶点纹理单元32MAX_TEXTURE_IMAGE_UNITS片元纹理单元32MAX_VARYING_VECTORS可变向量数30MAX_FRAGMENT_UNIFORM_VECTORS片元常量向量数1024这些数字不是乱写的——它们由 GPU 的硬件规格决定不同厂商、不同代际之间会有可区分的差异。把它们组合起来即使没有型号字符串也已经是一组能用来区分设备的信号。四、被忽略的一点屏蔽了型号还有能力参数这是大多数人没意识到的地方「屏蔽 WebGL 指纹」和「屏蔽显卡型号字符串」是两回事。Brave 的做法是直接屏蔽WEBGL_debug_renderer_info这个扩展拿到的扩展对象是 nullFirefox 开privacy.resistFingerprinting会把型号伪装成通用值。这些手段确实能挡住型号字符串那一层。但能力参数那一层很多情况下仍然可读——MAX_TEXTURE_SIZE、MAX_VIEWPORT_DIMS、扩展列表的长度和内容并不都随型号一起被隐藏。这和第 1 篇讲 Canvas、第 3 篇讲 WebRTC 是同一个道理只要返回一个被改动过的值就存在被还原或被失效的可能真正干净的是什么都不返回。而 WebGL 恰恰是一个你很难让它什么都不返回又保持正常功能的接口。五、动手查一段代码 一张记录表F12 打开控制台粘下面这段代码回车// WebGL 指纹读取脚本// 用法F12 打开控制台 - 粘贴 - 回车// 作用读取显卡型号UNMASKED 系列、能力参数、扩展数量并判断当前渲染环境(function(){use strict;varcanvasdocument.createElement(canvas);varglcanvas.getContext(webgl)||canvas.getContext(experimental-webgl);if(!gl){console.log(%c[结论] 本浏览器拿不到 WebGL 上下文。,color:#c0392b;font-weight:bold);console.log(可能原因浏览器禁用了 WebGL、或当前环境如无 GPU 的虚拟机、关闭了硬件加速不支持。);console.log(注意WebGL 不可用本身也是一个可被识别的特征未必等于“更安全”。);return;}functionp(name){try{returnString(gl.getParameter(gl[name]));}catch(e){return读取失败;}}// 能力参数 —— 这些在 debug 扩展被屏蔽后通常仍然可读varcaps{GL_VERSION:p(VERSION),SHADING_LANGUAGE_VERSION:p(SHADING_LANGUAGE_VERSION),MAX_TEXTURE_SIZE:p(MAX_TEXTURE_SIZE),MAX_VIEWPORT_DIMS:p(MAX_VIEWPORT_DIMS),MAX_RENDERBUFFER_SIZE:p(MAX_RENDERBUFFER_SIZE),MAX_VERTEX_ATTRIBS:p(MAX_VERTEX_ATTRIBS),MAX_VERTEX_TEXTURE_IMAGE_UNITS:p(MAX_VERTEX_TEXTURE_IMAGE_UNITS),MAX_TEXTURE_IMAGE_UNITS:p(MAX_TEXTURE_IMAGE_UNITS),MAX_VARYING_VECTORS:p(MAX_VARYING_VECTORS),MAX_FRAGMENT_UNIFORM_VECTORS:p(MAX_FRAGMENT_UNIFORM_VECTORS)};varextgl.getExtension(WEBGL_debug_renderer_info);varvendor扩展被屏蔽拿不到;varrenderer扩展被屏蔽拿不到;if(ext){vendorString(gl.getParameter(ext.UNMASKED_VENDOR_WEBGL));rendererString(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));}varextCount0;try{extCount(gl.getSupportedExtensions()||[]).length;}catch(e){}console.log(%c 一、显卡型号UNMASKED 系列,font-weight:bold;font-size:13px);console.log(UNMASKED_VENDOR_WEBGL vendor);console.log(UNMASKED_RENDERER_WEBGLrenderer);console.log(debug 扩展是否可用(ext?可用:被屏蔽Brave / Tor / Safari 会走到这里));console.log(%c 二、能力参数debug 扩展被屏蔽后仍可读,font-weight:bold;font-size:13px);if(console.table){console.table(Object.keys(caps).map(function(k){return{参数:k,值:caps[k]};}));}else{Object.keys(caps).forEach(function(k){console.log(kcaps[k]);});}console.log(支持的扩展数量extCount);console.log(%c 三、结论 ,font-weight:bold;font-size:13px);if(extrenderer.indexOf(SwiftShader)-1){console.log(%c! RENDERER 里出现 SwiftShader说明当前是 CPU 软渲染 —— 常见于虚拟机、关闭硬件加速、或驱动被拉黑的环境。,color:#e67e22);}elseif(ext){console.log(%c× 能读到真实显卡型号。,color:#c0392b);console.log( 改 UA、改语言、改屏幕参数都不会影响这一项 —— 它直接来自 GPU 硬件。);}else{console.log(%c√ debug 扩展被屏蔽拿不到真实显卡型号字符串。,color:#27ae60);console.log( 但要继续看「能力参数」那一组 —— 它们仍可能暴露 GPU 的型号与代际。);}console.log();console.log(提示不同浏览器结果不同Brave 屏蔽 / Firefox RFP 伪装 / Chrome 直出。);console.log(提示请在你实际跑业务的那个浏览器配置里测而不是用一个干净的新配置测。);})();它分三块输出显卡型号UNMASKED 系列、能力参数、结论。结论会区分三种情况——读到真实显卡 / SwiftShader 软渲染 / 扩展被屏蔽。10 组真机取值记录表下面是你要自己填的部分也是这篇能压过竞品软文的地方。把你的每台机器、每个环境跑一遍把UNMASKED_RENDERER_WEBGL那一栏抄下来环境编号浏览器RENDERER 读出值VENDORMAX_TEXTURE_SIZE备注物理机/虚拟机/云桌面01020304050607080910填这张表时注意三件事同一块物理 GPU 上跑的多个环境RENDERER 字符串会高度一致——这不是巧合是它们共用同一块硬件。虚拟机里如果出现Google SwiftShader说明在走 CPU 软渲染没有透传 GPU或硬件加速被关。SwiftShader 本身也是一个可识别的特征。两台看起来一样的机器只要显卡型号或驱动版本不同字符串就会不同——这也是为什么填表比背模板有用。六、各浏览器现在的处置方式浏览器对 WebGL 型号的处理说明Chrome / Edge直出真实值默认无防护WEBGL_debug_renderer_info可读Firefox默认直出开privacy.resistFingerprinting后伪装成通用值FirefoxRFP伪装型号变成通用字符串部分能力参数被掩盖Brave屏蔽扩展 逐站随机化Shields 激活时扩展对象为 null其余参数加噪Safari默认屏蔽该扩展型号不可读Tor Browser安全级别下禁用 WebGLSafer/Safest 级别直接拿不到上下文七、三个还在流传的误解误解一改了 UA 就换了身份。UA 是自报家门显卡是硬件实况。前者改得再彻底后者一个字没变。误解二屏蔽了 renderer 字符串WebGL 就干净了。型号挡住了能力参数、扩展列表还在。这部分依然携带可区分的硬件信息。误解三虚拟机里的 WebGL 都一样都是 SwiftShader。SwiftShader 本身是特征而且走了软渲染这件事恰恰说明你处在某个特殊环境里。一堆环境全部报 SwiftShader反而更容易被归成一类。八、为什么这件事在多店铺场景里重要回到「多店铺环境隔离」的语境结论很直接如果多个店铺环境跑在同一块物理 GPU上那么无论你把 UA、语言、时区、屏幕参数改成什么样它们的UNMASKED_RENDERER_WEBGL字符串和那一组能力参数天然就是一致的——因为硬件没变过。环境隔离如果只做在能改的软件参数这一层显卡这一项就会成为一个稳定的、跨环境不变的锚点。这也是为什么说真正需要被隔离的是那些你改不动的东西而不是那些你能改的东西。九、自查清单每个店铺环境跑一遍第五节的脚本把 RENDERER 值抄进记录表对照同一块物理 GPU 上的多个环境确认它们的取值是否一致虚拟机环境是否出现 SwiftShader是否属于预期内的软渲染是否有环境意外读到真实显卡型号该屏蔽的没屏蔽浏览器更新后重测——驱动、ANGLE 版本都会改变返回值这份清单的作用是帮你判断显卡这一层自己的多个环境之间是什么样的关系。任何涉及环境隔离的具体操作请以相关平台的官方规则和你自己的合规要求为准。你的WEBGL_debug_renderer_info读出来是真实显卡型号还是通用值 / SwiftShader把你跑出来的 RENDERER 那一串贴在评论区看看型号可以打码。相关阅读[第 1 篇] 亚马逊判定账号关联时Canvas 指纹这一项到底在比什么[第 3 篇] 换了 IP 还是被关联WebRTC 泄露本地 IP 的三种触发路径[待发布] 用 BrowserLeaks 给自己做个指纹体检报告逐项解读本文所述为浏览器与图形 API 的客观机制说明用于理解指纹构成不构成任何操作建议。相关平台规则请以官方最新公告为准。