瀏覽器指紋防禦指南:從 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#反追踪