浏览器指纹防御指南:从 Canvas 指纹到 Privacy-first 的反追踪策略

前端工程(更新于 2026年6月2日)

即使禁用 Cookie、使用隐身模式,网站仍可通过浏览器指纹唯一标识你——准确率高达 99.1%。

指纹类型 稳定性 唯一性 防御难度
User-Agent 低(随更新变化)
Canvas
WebGL
AudioContext
字体枚举
屏幕分辨率
时区/语言

一、Canvas 指纹

原理

不同设备的 GPU、驱动、抗锯齿算法会导致同一 Canvas 绘制结果有微小差异:

function getCanvasFingerprint() {
  const canvas = document.createElement('canvas');
  canvas.width = 200;
  canvas.height = 50;
  const ctx = canvas.getContext('2d');

  // 绘制文本和图形
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillStyle = '#f60';
  ctx.fillRect(125, 1, 62, 20);
  ctx.fillStyle = '#069';
  ctx.fillText('Hello, world! 🌍', 2, 15);
  ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
  ctx.fillText('Hello, world! 🌍', 4, 17);

  // 提取像素数据并哈希
  const data = canvas.toDataURL();
  return hash(data); // 如 "a1b2c3d4..."
}

防御方法

方法一:注入噪声

// 重写 toDataURL,添加微小随机噪声
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;

HTMLCanvasElement.prototype.toDataURL = function (...args) {
  const ctx = this.getContext('2d');
  if (ctx) {
    // 在不显眼的位置添加随机像素
    const imageData = ctx.getImageData(0, 0, this.width, this.height);
    const noise = Math.random() * 0.01;
    imageData.data[0] = Math.min(255, imageData.data[0] + noise);
    ctx.putImageData(imageData, 0, 0);
  }
  return originalToDataURL.apply(this, args);
};

方法二:返回固定值

// 所有 Canvas 返回相同的空白图像
HTMLCanvasElement.prototype.toDataURL = function () {
  return 'data:image/png;base64,...'; // 固定空白图像
};

二、WebGL 指纹

原理

WebGL 暴露 GPU 渲染器、供应商等信息,且不同 GPU 的渲染结果有差异:

function getWebGLFingerprint() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl');

  const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
  const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
  const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);

  // 渲染测试场景
  // ... 不同 GPU 产生不同像素结果

  return { vendor, renderer, renderHash };
}

常见泄露信息

信息 示例
GPU 供应商 "NVIDIA Corporation", "Intel", "AMD"
GPU 渲染器 "GeForce RTX 4090", "Apple M2"
最大纹理尺寸 16384, 8192
最大顶点属性 16
支持的扩展列表 30+ 个扩展名

防御方法

// 隐藏 GPU 信息
const getParameter = WebGLRenderingContext.prototype.getParameter;

WebGLRenderingContext.prototype.getParameter = function (param) {
  if (param === 37445) return 'Intel Inc.';    // UNMASKED_VENDOR
  if (param === 37446) return 'Intel Iris';    // UNMASKED_RENDERER
  return getParameter.call(this, param);
};

三、AudioContext 指纹

原理

不同设备的音频处理单元(DSP)对同一信号的处理结果有微小差异:

function getAudioFingerprint() {
  const context = new OfflineAudioContext(1, 44100, 44100);

  const oscillator = context.createOscillator();
  oscillator.type = 'triangle';
  oscillator.frequency.setValueAtTime(10000, context.currentTime);

  const compressor = context.createDynamicsCompressor();
  compressor.threshold.setValueAtTime(-50, context.currentTime);
  compressor.knee.setValueAtTime(40, context.currentTime);
  compressor.ratio.setValueAtTime(12, context.currentTime);

  oscillator.connect(compressor);
  compressor.connect(context.destination);
  oscillator.start(0);

  return context.startRendering().then(buffer => {
    const data = buffer.getChannelData(0);
    return hash(data.slice(4500, 5000));
  });
}

防御方法

// 对 AudioContext 输出添加噪声
const originalStartRendering = OfflineAudioContext.prototype.startRendering;

OfflineAudioContext.prototype.startRendering = function () {
  return originalStartRendering.call(this).then(buffer => {
    const data = buffer.getChannelData(0);
    for (let i = 0; i < data.length; i++) {
      data[i] += (Math.random() - 0.5) * 1e-7;
    }
    return buffer;
  });
};

四、字体枚举指纹

原理

通过测量不同字体名的渲染宽度,判断用户安装了哪些字体:

function detectFonts() {
  const testFonts = [
    'Arial', 'Helvetica', 'Times New Roman',
    'Courier', 'Verdana', 'Georgia',
    'Comic Sans MS', 'Impact', 'Tahoma',
    'Microsoft YaHei', 'PingFang SC',
  ];

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  const baseFonts = ['monospace', 'serif', 'sans-serif'];
  const results = {};

  for (const font of testFonts) {
    for (const base of baseFonts) {
      ctx.font = `72px ${base}`;
      const baseWidth = ctx.measureText('mmmmmmmmll').width;
      ctx.font = `72px "${font}", ${base}`;
      const testWidth = ctx.measureText('mmmmmmmmll').width;
      if (baseWidth !== testWidth) {
        results[font] = true;
        break;
      }
    }
  }

  return results;
}

防御方法

字体枚举最难防御,因为它利用的是浏览器正常的文本渲染能力。主要策略:

  1. 限制字体加载:只允许系统字体
  2. CSS font-display: block:延迟非必要字体
  3. 浏览器隐私设置:Firefox 的 privacy.resistFingerprinting

五、综合指纹熵分析

信息维度 熵 (bits) 累计唯一性
User-Agent ~10 2^10 = 1024
屏幕分辨率 ~5 2^15
时区 ~5 2^20
已安装字体 ~15 2^35
Canvas ~15 2^50
WebGL ~10 2^60
AudioContext ~5 2^65

2^65 ≈ 3.7 × 10^19,足以唯一标识地球上每一台设备。


六、Privacy-First 应用架构

工具库网站的隐私策略

用户文件 → 浏览器本地处理 → 结果直接下载
         ↑
    不经过服务器
    不存储任何数据
    不设置追踪 Cookie
    不收集浏览器指纹

实施清单

策略 实现 优先级
不使用第三方追踪 移除 Google Analytics 等脚本 P0
CSP 限制外部资源 default-src 'self' P0
不设置 Cookie 纯静态站无需 Cookie P0
Referrer-Policy strict-origin-when-cross-origin P1
Permissions-Policy 限制 API 访问 P1
隐私声明 明确数据处理方式 P1
本地处理架构 所有计算在浏览器端完成 P0

HTTP 安全头配置

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

七、检测自己的指纹

推荐使用以下工具检测浏览器指纹唯一性:

  • AmIUnique (amiunique.org):全面指纹检测
  • BrowserLeaks (browserleaks.com):Canvas/WebGL/Audio 详细分析
  • FingerprintJS (fingerprintjs.com):开源指纹库 Demo

减少指纹的建议

  1. 使用 Firefox + privacy.resistFingerprinting = true
  2. 使用 Tor Browser(统一指纹)
  3. 使用隐私扩展(Privacy Badger、uBlock Origin)
  4. 避免安装稀有字体
  5. 使用标准分辨率和缩放比例

本站提供浏览器本地工具,免注册即可试用 →

#浏览器指纹#隐私安全#Canvas#WebGL#反追踪