---
title: "Canvas指纹与WebGL指纹：为什么换了 IP 网站还能认出同一台电脑"
description: "Canvas指纹和WebGL指纹来自显卡、驱动和字体的渲染差异，换 IP、开无痕都不会变。本文给出可在控制台运行的检测代码，并对比 Tor、Firefox、Brave、Safari 内置防护的做法与代价。"
url: https://cdnping.org/blog/canvas-webgl-fingerprint-explained
language: zh-CN
published: 2026-10-24
category: "IP 与隐私"
tags: ["Canvas指纹","WebGL指纹","浏览器指纹","隐私保护","硬件指纹"]
source: cdnping.org
license: Free to quote and summarize with a link to the url above.
---
# Canvas指纹与WebGL指纹：为什么换了 IP 网站还能认出同一台电脑

换了 IP 还能被网站认出来，常见原因之一是 Canvas指纹和 WebGL 指纹。网页让浏览器画一段文字或一个 3D 场景，再把画出来的像素读回去算哈希。像素结果由显卡、驱动、操作系统和字体渲染方式决定，和 IP 无关，所以换线路、开无痕窗口、清 Cookie 之后，这个值通常不变。

本文先用两段能直接粘进浏览器控制台的代码，让你亲眼看到自己的渲染哈希和显卡型号；再解释它为什么这么稳定；最后对比几种主流浏览器内置防护的思路和代价。结果可以和 [cdnping 的 IP 与环境检测](/ip) 里的“硬件指纹”一项对照，这部分在本地计算，不上传。

## Canvas 指纹到底是怎么算出来的？

Canvas 是浏览器的 2D 绘图接口。网页可以在一块不显示出来的画布上写字、画渐变、叠 emoji，然后调用 `toDataURL()` 或 `getImageData()` 把像素读出来。

同样一句“Hello, 你好”，在不同机器上画出来的像素并不完全一致。差异来自：

- 字体回退：系统里有没有指定字体，缺了用哪个替代。
- 抗锯齿和次像素渲染：Windows、macOS、Linux 的文字平滑策略不同。
- GPU 和驱动：开启硬件加速时，部分绘制交给显卡，不同显卡和驱动版本的浮点误差、混色方式略有差别。
- emoji 字库：各系统自带的 emoji 图形完全不同。

肉眼看不出这些差别，但哈希值会完全不同。网站并不关心图像本身，只关心这个哈希。

## 自己动手：在控制台看你的 Canvas 哈希

打开任意网页，按 F12（macOS 上是 Cmd+Option+I）进入控制台，粘贴下面这段代码回车。它只在本地运行，不发送任何数据。

```js
(async () => {
  const c = document.createElement('canvas');
  c.width = 280; c.height = 60;
  const ctx = c.getContext('2d');
  ctx.textBaseline = 'top';
  ctx.font = '16px Arial';
  ctx.fillStyle = '#f60';
  ctx.fillRect(100, 1, 62, 20);
  ctx.fillStyle = '#069';
  ctx.fillText('Hello, 你好 🌏 1.0', 2, 15);
  ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
  ctx.fillText('Hello, 你好 🌏 1.0', 4, 17);
  const data = new TextEncoder().encode(c.toDataURL());
  const buf = await crypto.subtle.digest('SHA-256', data);
  const hex = [...new Uint8Array(buf)].map(b => b.toString(16).padStart(2, '0')).join('');
  console.log('Canvas 哈希:', hex.slice(0, 16));
})();
```

输出是一串 16 位的十六进制字符。记下它，后面验证稳定性时要用。

## WebGL 指纹能读出我的显卡型号吗？

能，而且往往是原样读出。WebGL 是浏览器里的 3D 接口，它有一个调试扩展 `WEBGL_debug_renderer_info`，可以返回显卡厂商和型号字符串。继续在控制台粘贴：

```js
(() => {
  const gl = document.createElement('canvas').getContext('webgl');
  if (!gl) return console.log('WebGL 不可用');
  const ext = gl.getExtension('WEBGL_debug_renderer_info');
  console.log('厂商:', ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR));
  console.log('渲染器:', ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER));
  console.log('最大纹理尺寸:', gl.getParameter(gl.MAX_TEXTURE_SIZE));
  console.log('扩展数量:', gl.getSupportedExtensions().length);
})();
```

在 Windows 的 Chromium 系浏览器上，渲染器一栏通常是 `ANGLE (厂商, 显卡型号, Direct3D11 ...)` 这样的格式，显卡型号写得很清楚。Firefox 和 Safari 会对这个字符串做不同程度的概括，例如 Safari 上常见的只是“Apple GPU”。

除了型号字符串，WebGL 指纹还包括两部分：

1. 参数和扩展列表：最大纹理尺寸、支持的着色器精度、扩展名单等几十项，组合起来就有区分度。
2. 渲染哈希：和 Canvas 一样，让显卡画一个带光照的 3D 图形再读回像素。这一项直接反映 GPU 和驱动的计算差异，所以也常被叫作“显卡指纹”。

## 音频指纹又是什么？

原理相同，只是换成了声音。网页用 `OfflineAudioContext` 在内存里生成一段振荡波，经过压缩器处理后取样本求和，整个过程不播放任何声音。不同 CPU、系统音频栈和浏览器实现，在浮点运算上的细小差别会让结果的末几位小数不同。

音频指纹的区分度一般不如 Canvas 和 WebGL，但它和前两者互相独立，组合使用时能进一步缩小范围。

## 为什么换 IP、开无痕、重启都不变？

因为这些值不存储在任何地方，每次都是现场算出来的。只要“谁来画”没变，结果就不变。可以用下面的表判断哪些操作会让它变化：

| 你做的操作 | Canvas / WebGL 指纹是否变化 | 原因 |
| --- | --- | --- |
| 换 IP、开关代理 | 不变 | 渲染与网络无关 |
| 无痕窗口、清 Cookie | 不变 | 指纹不依赖本地存储 |
| 重启浏览器、重启电脑 | 不变 | 硬件和驱动没变 |
| 更新显卡驱动 | 可能变化 | 渲染路径变了 |
| 浏览器大版本升级 | 可能变化 | 图形后端或字体渲染调整 |
| 开关硬件加速 | 通常变化 | 从 GPU 渲染切到软件渲染 |
| 新装或删除字体 | Canvas 可能变化 | 字体回退结果不同 |
| 换一台同型号电脑 | 可能相同 | 同配置同系统的机器结果接近 |

最后一行说明，Canvas 指纹单独用并不能精确到“某一个人”。同一批采购、同一系统镜像的办公电脑，哈希很可能一样。所以网站一般会把它和 UA、屏幕、时区、字体等特征一起用，这部分在[浏览器指纹是什么：网站不靠 Cookie 能认出你的 20 多项特征与自查方法](/blog/browser-fingerprint-explained-self-check)里有完整拆解。

这也解释了标题里的问题：IP 只是网络层的身份，换 IP 改变的是“你从哪里来”，没有改变“你用什么设备”。关于 IP 本身能暴露什么，可以看[你的 IP 地址到底暴露了什么](/blog/what-your-ip-address-reveals)。

## 一份可以照做的稳定性验证步骤

用前面两段代码，加上 cdnping 的检测页，按下面的顺序验证：

1. 在普通窗口运行 Canvas 代码和 WebGL 代码，记下哈希和渲染器字符串。
2. 打开同一浏览器的无痕窗口，重复一次。多数浏览器下两次结果一致。
3. 完全退出浏览器再打开，重复一次。
4. 在浏览器设置里关闭“使用硬件加速”，重启后再运行。哈希大概率变化，渲染器可能变成 SwiftShader 之类的软件渲染器。验证完记得改回来。
5. 打开 [/ip](/ip)，看“硬件指纹”一项给出的显卡和渲染信息，和控制台结果对照。
6. 如果用的是带指纹保护的浏览器，在同一站点刷新几次、或换一个站点运行，观察哈希是否每次不同。

第 6 步能直接告诉你，当前浏览器用的是哪一类防护。

## 浏览器内置的防护有哪几种思路？

主流做法可以归为三类，目标都是让网站拿不到稳定、独特的值，但代价不同。

### 阻止读取：Tor Browser

Tor Browser 在网页尝试读取 Canvas 像素时会拦下来，默认返回空白数据，并可能提示用户是否允许。WebGL 相关信息也被限制。效果最直接，代价是依赖读取 Canvas 的正常功能（例如在线图片编辑、截图分享、部分验证码）会失灵。

### 统一化：Firefox 的 resistFingerprinting

Firefox 在 `about:config` 中有一个 `privacy.resistFingerprinting` 选项。开启后，它的思路是让所有用户看起来一样：Canvas 读取需要授权，时区报成 UTC，部分语言和窗口尺寸也会被统一。这个选项最初来自 Tor Browser 合并回 Firefox 的补丁，Mozilla 并不建议普通用户长期打开，因为时区错乱会影响日历、网页时间显示，一些网站会表现异常。

Firefox 新版本另有一套范围更小的指纹保护，在增强型跟踪保护的“严格”模式和隐私窗口中生效，对 Canvas 读取加入少量噪声，副作用比 resistFingerprinting 小。

### 随机化：Brave 和 Safari

Brave 的 Shields 默认对 Canvas、WebGL 和音频读取加入轻微扰动，扰动按站点、按会话变化。结果是同一个网站在本次会话里拿到的值稳定，换一个网站或重开浏览器就变了，跨站把你串起来的难度因此上升。Safari 的高级指纹保护在无痕浏览中也采用了加噪的方式。

三种思路对照如下：

| 思路 | 代表 | 网站看到什么 | 主要代价 |
| --- | --- | --- | --- |
| 阻止读取 | Tor Browser | 空白或固定的占位值 | 依赖 Canvas 的功能失效 |
| 统一化 | Firefox resistFingerprinting | 和其他同类用户一样的值 | 时区、语言被改，部分网站异常 |
| 随机化 | Brave、Safari 无痕、Firefox 严格模式 | 每站每会话不同的值 | 少数检测会把“值在变”本身当成异常 |

## 选哪种防护比较合适？

先想清楚目的。如果你只是不想被广告网络跨站追踪，Brave 的默认设置或 Firefox 的严格模式已经够用，日常功能基本不受影响。如果你需要高强度匿名，Tor Browser 的统一化方案更彻底，因为“大家都一样”比“每次都不一样”更难被区分。

有几点经常被忽略：

- 改得越多，越显眼。手动装多个修改指纹的扩展，往往会让 Canvas、WebGL、UA 之间互相矛盾。这种不一致本身就是很强的特征，[浏览器指纹与环境检测：时区、语言、字体和 IP 对不上，各自暴露了什么](/blog/browser-environment-consistency)讲的就是这个问题。
- 指纹保护不等于网络隐私。WebRTC 泄漏和 DNS 泄漏会直接暴露真实 IP，和渲染指纹是两回事，需要分别处理，可参考 [WebRTC 泄漏怎么关](/blog/fix-webrtc-leak-chrome-firefox-safari) 和 [DNS 泄漏与 WebRTC 泄漏原理](/learn/dns-leak-webrtc-leak)。
- 时区和语言同样是指纹。只处理 Canvas，时区和 `Accept-Language` 照样能缩小范围，详见[时区和语言也会暴露你](/blog/timezone-language-leak-check)。

还要说明的是，银行、支付、账号登录等场景用设备指纹做风控，是为了识别盗号和欺诈。本文讨论的是普通用户如何理解和减少被跨站追踪，不涉及绕过任何平台的风控或检测，使用相关服务时请遵守其服务条款和当地法规。

## 在 cdnping /ip 里怎么看硬件指纹结果？

[/ip](/ip) 里有“硬件指纹”和“时区语言字体”两项检测，和本文的控制台代码看的是同一类设备特征。指纹都在你的浏览器本地计算，不上传；生成带水印的结果图时可以选择隐藏 IP。

把控制台输出和检测页结果放在一起，可以这样判断：

- 渲染器字符串里有具体显卡型号：说明当前浏览器没有屏蔽这项，区分度较高。
- 渲染器显示为概括名称或软件渲染器：浏览器做了概括，或硬件加速未开启。
- 刷新后 Canvas 结果变化：浏览器在做随机化。
- Canvas 结果为空或读取失败：浏览器在阻止读取。

每一项检测的具体含义，可以配合[浏览器环境检测怎么看：13 项检测逐项解读](/blog/browser-environment-check-explained)一起看；检测口径和数据处理方式写在[数据方法](/methodology)页面。
