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 指紋還包括兩部分:
- 引數和擴充套件列表:最大紋理尺寸、支援的著色器精度、副檔名單等幾十項,組合起來就有區分度。
- 渲染雜湊:和 Canvas 一樣,讓顯示卡畫一個帶光照的 3D 圖形再讀回畫素。這一項直接反映 GPU 和驅動的計算差異,所以也常被叫作“顯示卡指紋”。
音訊指紋又是什麼?
原理相同,只是換成了聲音。網頁用 OfflineAudioContext 在記憶體裡生成一段振盪波,經過壓縮器處理後取樣本求和,整個過程不播放任何聲音。不同 CPU、系統音訊棧和瀏覽器實現,在浮點運算上的細小差別會讓結果的末幾位小數不同。
音訊指紋的區分度一般不如 Canvas 和 WebGL,但它和前兩者互相獨立,組合使用時能進一步縮小範圍。
為什麼換 IP、開無痕、重啟都不變?
因為這些值不儲存在任何地方,每次都是現場算出來的。只要“誰來畫”沒變,結果就不變。可以用下面的表判斷哪些操作會讓它變化:
| 你做的操作 | Canvas / WebGL 指紋是否變化 | 原因 |
|---|---|---|
| 換 IP、開關代理 | 不變 | 渲染與網路無關 |
| 無痕視窗、清 Cookie | 不變 | 指紋不依賴本地儲存 |
| 重啟瀏覽器、重啟電腦 | 不變 | 硬體和驅動沒變 |
| 更新顯示卡驅動 | 可能變化 | 渲染路徑變了 |
| 瀏覽器大版本升級 | 可能變化 | 圖形後端或字型渲染調整 |
| 開關硬體加速 | 通常變化 | 從 GPU 渲染切到軟體渲染 |
| 新裝或刪除字型 | Canvas 可能變化 | 字型回退結果不同 |
| 換一臺同型號電腦 | 可能相同 | 同配置同系統的機器結果接近 |
最後一行說明,Canvas 指紋單獨用並不能精確到“某一個人”。同一批採購、同一系統映象的辦公電腦,雜湊很可能一樣。所以網站一般會把它和 UA、螢幕、時區、字型等特徵一起用,這部分在瀏覽器指紋是什麼:網站不靠 Cookie 能認出你的 20 多項特徵與自查方法裡有完整拆解。
這也解釋了標題裡的問題:IP 只是網路層的身份,換 IP 改變的是“你從哪裡來”,沒有改變“你用什麼裝置”。關於 IP 本身能暴露什麼,可以看你的 IP 地址到底暴露了什麼。
一份可以照做的穩定性驗證步驟
用前面兩段程式碼,加上 cdnping 的檢測頁,按下面的順序驗證:
- 在普通視窗執行 Canvas 程式碼和 WebGL 程式碼,記下雜湊和渲染器字串。
- 開啟同一瀏覽器的無痕視窗,重複一次。多數瀏覽器下兩次結果一致。
- 完全退出瀏覽器再開啟,重複一次。
- 在瀏覽器設定裡關閉“使用硬體加速”,重啟後再執行。雜湊大機率變化,渲染器可能變成 SwiftShader 之類的軟體渲染器。驗證完記得改回來。
- 開啟 /ip,看“硬體指紋”一項給出的顯示卡和渲染資訊,和控制檯結果對照。
- 如果用的是帶指紋保護的瀏覽器,在同一站點重新整理幾次、或換一個站點執行,觀察雜湊是否每次不同。
第 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 對不上,各自暴露了什麼講的就是這個問題。
- 指紋保護不等於網路隱私。WebRTC 洩漏和 DNS 洩漏會直接暴露真實 IP,和渲染指紋是兩回事,需要分別處理,可參考 WebRTC 洩漏怎麼關 和 DNS 洩漏與 WebRTC 洩漏原理。
- 時區和語言同樣是指紋。只處理 Canvas,時區和
Accept-Language照樣能縮小範圍,詳見時區和語言也會暴露你。
還要說明的是,銀行、支付、賬號登入等場景用裝置指紋做風控,是為了識別盜號和欺詐。本文討論的是普通使用者如何理解和減少被跨站追蹤,不涉及繞過任何平臺的風控或檢測,使用相關服務時請遵守其服務條款和當地法規。
在 cdnping /ip 裡怎麼看硬體指紋結果?
/ip 裡有“硬體指紋”和“時區語言字型”兩項檢測,和本文的控制檯程式碼看的是同一類裝置特徵。指紋都在你的瀏覽器本地計算,不上傳;生成帶水印的結果圖時可以選擇隱藏 IP。
把控制檯輸出和檢測頁結果放在一起,可以這樣判斷:
- 渲染器字串裡有具體顯示卡型號:說明當前瀏覽器沒有遮蔽這項,區分度較高。
- 渲染器顯示為概括名稱或軟體渲染器:瀏覽器做了概括,或硬體加速未開啟。
- 重新整理後 Canvas 結果變化:瀏覽器在做隨機化。
- Canvas 結果為空或讀取失敗:瀏覽器在阻止讀取。
每一項檢測的具體含義,可以配合瀏覽器環境檢測怎麼看:13 項檢測逐項解讀一起看;檢測口徑和資料處理方式寫在資料方法頁面。