瀏覽器指紋防禦指南:從 Canvas 指紋到 Privacy-first 的反追蹤策略
前端工程(更新於 2026年6月2日)
瀏覽器指紋:無 Cookie 的追蹤技術
即使禁用 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;
}
防禦方法
字型列舉最難防禦,因為它利用的是瀏覽器正常的文字渲染能力。主要策略:
- 限制字型載入:只允許系統字型
- CSS font-display: block:延遲非必要字型
- 瀏覽器隱私設定: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
減少指紋的建議:
- 使用 Firefox +
privacy.resistFingerprinting = true - 使用 Tor Browser(統一指紋)
- 使用隱私擴充(Privacy Badger、uBlock Origin)
- 避免安裝稀有字型
- 使用標準解析度和縮放比例
本站提供瀏覽器本地工具,免註冊即可試用 →
#浏览器指纹#隐私安全#Canvas#WebGL#反追踪