WebGL 指纹检测原理
WebGL(Web Graphics Library)指纹是浏览器指纹中最具硬件特征的部分。它通过暴露 GPU 型号、驱动版本和渲染能力参数,为每台设备生成高度唯一的标识。
为什么 WebGL 能识别设备
每一台设备的显卡组合(GPU 芯片 + 驱动版本 + 操作系统图形接口)几乎都是唯一的。即使是同一型号的显卡,不同驱动版本也会暴露不同的参数和能力。
核心检测方法
方法一:GPU vendor 和 renderer 直读
function getWebGLBasicInfo() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return { supported: false };
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
return {
supported: true,
vendor: gl.getParameter(gl.VENDOR),
renderer: gl.getParameter(gl.RENDERER),
// 需要扩展支持才能获取真实 GPU 信息
unmaskedVendor: debugInfo
? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)
: null,
unmaskedRenderer: debugInfo
? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: null,
version: gl.getParameter(gl.VERSION),
shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION)
};
}
UNMASKED_VENDOR_WEBGL 和 UNMASKED_RENDERER_WEBGL 会返回真实的 GPU 厂商和型号,例如:
- Vendor: “NVIDIA Corporation”
- Renderer: “NVIDIA GeForce RTX 3060/PCIe/SSE2”
方法二:渲染能力参数枚举
function getWebGLCapabilities(gl) {
return {
aliasedLineWidthRange: gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE),
aliasedPointSizeRange: gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE),
alphaBits: gl.getParameter(gl.ALPHA_BITS),
blueBits: gl.getParameter(gl.BLUE_BITS),
depthBits: gl.getParameter(gl.DEPTH_BITS),
greenBits: gl.getParameter(gl.GREEN_BITS),
redBits: gl.getParameter(gl.RED_BITS),
maxCombinedTextureImageUnits: gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),
maxCubeMapTextureSize: gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE),
maxFragmentUniformVectors: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
maxRenderBufferSize: gl.getParameter(gl.MAX_RENDER_BUFFER_SIZE),
maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),
maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
maxVaryingVectors: gl.getParameter(gl.MAX_VARYING_VECTORS),
maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
maxVertexUniformVectors: gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),
precisionFormats: {
lowFloat: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.LOW_FLOAT),
mediumFloat: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT),
highFloat: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT)
}
};
}
方法三:扩展支持列表
function getWebGLExtensions(gl) {
const extensions = gl.getSupportedExtensions();
return {
count: extensions ? extensions.length : 0,
list: extensions || [],
hasDebugRenderer: extensions && extensions.includes('WEBGL_debug_renderer_info'),
hasLoseContext: extensions && extensions.includes('WEBGL_lose_context')
};
}
方法四:渲染指纹(与 Canvas 类似)
function getWebGLRenderFingerprint() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 128;
const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true });
if (!gl) return null;
// 创建简单的着色器程序
const vsSource = `
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
}
`;
const fsSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(0.8, 0.3, 0.1, 0.9);
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
gl.useProgram(program);
// 绘制三角形
const vertices = new Float32Array([
-0.9, -0.9, 0.0,
0.9, -0.9, 0.0,
0.0, 0.9, 0.0
]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const aPosition = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
gl.clearColor(0.1, 0.2, 0.3, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// 读取像素
const pixels = new Uint8Array(256 * 128 * 4);
gl.readPixels(0, 0, 256, 128, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
return pixels;
}
WebGL 指纹的差异来源
| 差异来源 | 影响参数 | 唯一性贡献 |
|---|---|---|
| GPU 芯片型号 | UNMASKED_RENDERER | 极高 |
| 显卡驱动版本 | 渲染精度、扩展列表 | 高 |
| 操作系统 | 图形 API(DirectX / OpenGL / Metal) | 高 |
| 浏览器内核 | WebGL 实现细节 | 中 |
| 驱动设置 | 抗锯齿、各向异性过滤 | 中 |
在 EasyBR 中修改 WebGL 指纹
EasyBR 通过 Chromium 内核层面的修改实现 WebGL 指纹保护:
- Vendor/Renderer 重写:将真实 GPU 信息替换为通用值(如 “Google Inc. (NVIDIA)” / “ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Direct3D11…)”)
- 参数规范化:将 maxTextureSize 等参数统一为常见值
- 扩展列表裁剪:移除部分可能暴露真实硬件的扩展
注意:WebGL 指纹修改在浏览器内核层完成,
比 JavaScript 层的修改更稳定、更难被检测。
但过度修改(如将 NVIDIA 改为 Intel 核显参数)可能导致
WebGL 内容渲染异常,建议使用预设模板而非手动配置。
风险边界
- WebGL 修改可能导致渲染异常:部分依赖特定 GPU 能力的网站(如 WebGL 游戏、3D 预览)可能无法正常显示
- 性能差异可被检测:如果声称使用高端显卡但渲染性能明显偏低,可能被识别为虚拟机或修改环境
- 扩展列表不一致:声称支持某扩展但实际功能异常,会被标记
- 不保证 100% 通过检测:WebGL 指纹只是防关联的一个环节,需配合 IP、Cookie、Canvas 等多个维度
检测 WebGL 是否被修改
// 在浏览器控制台运行,检查 WebGL 信息
const c = document.createElement('canvas');
const gl = c.getContext('webgl');
const ext = gl.getExtension('WEBGL_debug_renderer_info');
console.log('Vendor:', gl.getParameter(gl.VENDOR));
console.log('Renderer:', gl.getParameter(gl.RENDERER));
if (ext) {
console.log('Unmasked Vendor:', gl.getParameter(ext.UNMASKED_VENDOR_WEBGL));
console.log('Unmasked Renderer:', gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));
}
FAQ
Q: WebGL 指纹和 Canvas 指纹有什么区别?
A: Canvas 指纹主要依赖 2D 渲染(字体、抗锯齿),WebGL 指纹依赖 GPU 的 3D 渲染管线和硬件参数。两者互补,建议同时保护。
Q: 所有浏览器都支持 WebGL 指纹采集吗?
A: 现代浏览器都支持 WebGL,但 WEBGL_debug_renderer_info 扩展在部分隐私模式下可能被禁用。即使禁用,渲染能力参数仍然可以采集。
Q: 虚拟机中的 WebGL 指纹和物理机一样吗?
A: 不一样。虚拟机通常使用虚拟 GPU 驱动(如 VMware SVGA、VirtualBox VMSVGA),其 WebGL 参数与物理 GPU 有明显区别,容易被检测。
Q: 移动端 WebGL 指纹有什么特点?
A: 移动端通常使用集成 GPU(Mali、Adreno、PowerVR),扩展列表较短,maxTextureSize 通常为 4096 或 8192,与桌面端差异明显。
Q: 如何验证 WebGL 指纹修改是否生效?
A: 使用浏览器指纹检测网站(如 browserleaks.com/webgl)对比修改前后的 vendor、renderer 和参数列表是否发生变化。
延伸阅读
先试 EasyBR,再决定是否扩团队或做更深的定制项目
标准版适合先验证多账号环境、代理和数据迁移;如果你需要更深的业务能力,我们也支持浏览器外包、Chromium 定制、贴牌浏览器与 Android 指纹浏览器开发。