Canvas指紋與WebGL指紋:為什麼換了 IP 網站還能認出同一臺電腦

部落格 / IP 與隱私

Canvas指紋與WebGL指紋:為什麼換了 IP 網站還能認出同一臺電腦

換了 IP 還能被網站認出來,常見原因之一是 Canvas指紋和 WebGL 指紋。網頁讓瀏覽器畫一段文字或一個 3D 場景,再把畫出來的畫素讀回去算雜湊。畫素結果由顯示卡、驅動、作業系統和字型渲染方式決定,和 IP 無關,所以換線路、開無痕視窗、清 Cookie 之後,這個值通常不變。

本文先用兩段能直接粘進瀏覽器控制檯的程式碼,讓你親眼看到自己的渲染雜湊和顯示卡型號;再解釋它為什麼這麼穩定;最後對比幾種主流瀏覽器內建防護的思路和代價。結果可以和 cdnping 的 IP 與環境檢測 裡的“硬體指紋”一項對照,這部分在本地計算,不上傳。

Canvas 指紋到底是怎麼算出來的?

Canvas 是瀏覽器的 2D 繪圖介面。網頁可以在一塊不顯示出來的畫布上寫字、畫漸變、疊 emoji,然後呼叫 toDataURL() 或 getImageData() 把畫素讀出來。

同樣一句“Hello, 你好”,在不同機器上畫出來的畫素並不完全一致。差異來自:

  • 字型回退:系統裡有沒有指定字型,缺了用哪個替代。
  • 抗鋸齒和次畫素渲染:Windows、macOS、Linux 的文字平滑策略不同。
  • GPU 和驅動:開啟硬體加速時,部分繪製交給顯示卡,不同顯示卡和驅動版本的浮點誤差、混色方式略有差別。
  • emoji 字型檔:各系統自帶的 emoji 圖形完全不同。

肉眼看不出這些差別,但雜湊值會完全不同。網站並不關心影象本身,只關心這個雜湊。

自己動手:在控制檯看你的 Canvas 雜湊

開啟任意網頁,按 F12(macOS 上是 Cmd+Option+I)進入控制檯,貼上下面這段程式碼回車。它只在本地執行,不傳送任何資料。

(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,可以返回顯示卡廠商和型號字串。繼續在控制檯貼上:

(() => {
  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 多項特徵與自查方法裡有完整拆解。

這也解釋了標題裡的問題:IP 只是網路層的身份,換 IP 改變的是“你從哪裡來”,沒有改變“你用什麼裝置”。關於 IP 本身能暴露什麼,可以看你的 IP 地址到底暴露了什麼。

一份可以照做的穩定性驗證步驟

用前面兩段程式碼,加上 cdnping 的檢測頁,按下面的順序驗證:

  1. 在普通視窗執行 Canvas 程式碼和 WebGL 程式碼,記下雜湊和渲染器字串。
  2. 開啟同一瀏覽器的無痕視窗,重複一次。多數瀏覽器下兩次結果一致。
  3. 完全退出瀏覽器再開啟,重複一次。
  4. 在瀏覽器設定裡關閉“使用硬體加速”,重啟後再執行。雜湊大機率變化,渲染器可能變成 SwiftShader 之類的軟體渲染器。驗證完記得改回來。
  5. 開啟 /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 的統一化方案更徹底,因為“大家都一樣”比“每次都不一樣”更難被區分。

有幾點經常被忽略:

還要說明的是,銀行、支付、賬號登入等場景用裝置指紋做風控,是為了識別盜號和欺詐。本文討論的是普通使用者如何理解和減少被跨站追蹤,不涉及繞過任何平臺的風控或檢測,使用相關服務時請遵守其服務條款和當地法規。

在 cdnping /ip 裡怎麼看硬體指紋結果?

/ip 裡有“硬體指紋”和“時區語言字型”兩項檢測,和本文的控制檯程式碼看的是同一類裝置特徵。指紋都在你的瀏覽器本地計算,不上傳;生成帶水印的結果圖時可以選擇隱藏 IP。

把控制檯輸出和檢測頁結果放在一起,可以這樣判斷:

  • 渲染器字串裡有具體顯示卡型號:說明當前瀏覽器沒有遮蔽這項,區分度較高。
  • 渲染器顯示為概括名稱或軟體渲染器:瀏覽器做了概括,或硬體加速未開啟。
  • 重新整理後 Canvas 結果變化:瀏覽器在做隨機化。
  • Canvas 結果為空或讀取失敗:瀏覽器在阻止讀取。

每一項檢測的具體含義,可以配合瀏覽器環境檢測怎麼看:13 項檢測逐項解讀一起看;檢測口徑和資料處理方式寫在資料方法頁面。

相關文章

繼續閱讀