有个场景大家可能遇到过:无痕模式打开某个电商站,Cookie 干干净净,结果首页推荐的还是你昨天看过的那双鞋。这不是玄学,是因为有浏览器指纹(Browser Fingerprinting) 。
一、Cookie 是印章,指纹是长相
理解这件事,先把两种识别方式分清楚。
Cookie 像进夜店时手背上盖的荧光章。 门口给你盖一个,下次来照一下就知道你是老客。但这东西的所有权在你手里------回家一洗就没了。
指纹像你的长相、身高、口音、走路姿势。 你换件衣服没用,你改个名字也没用。保安扫一眼就认出来了:哦,又是那个个子挺高、走路有点外八的人。
区别在于:Cookie 是服务器 「存」 在你这儿的东西,你能删;指纹是服务器从你身上 「读」 出来的东西,你删不掉------因为这些信息本来就是浏览器为了正常工作必须对外暴露的。
二、单个特征不值钱,组合起来就有了识别度
关键概念是信息熵。
- "身高 175"------全国几千万人,等于没说
- "身高 175 + 左撇子"------缩小到几百万
- "身高 175 + 左撇子 + 会说粤语 + 出生在冰岛"------基本能锁定到个位数了
浏览器能读到的特征,前端同学应该都眼熟:
| 类别 | 具体内容 | 区分度 |
|---|---|---|
| 基础信息 | UA、语言、时区、屏幕分辨率、色深 | 低 |
| 硬件信息 | hardwareConcurrency、deviceMemory、触摸点数 |
中 |
| 字体列表 | 系统装了哪些字体(靠测量文本宽度探测) | 高 |
| Canvas | 绘制文本/图形后对像素做 hash | 高 |
| WebGL | GPU 厂商与型号字符串、渲染结果 hash | 高 |
| AudioContext | 音频振荡器输出的浮点数特征 | 中高 |
| 其他 | 插件列表、媒体设备、TLS 握手特征 | 中 |
EFF 早年做过一个叫 Panopticlick 的实验,结论很直白:绝大多数浏览器的特征组合在全网是唯一的。
三、重点看 Canvas:同一句话,可以用一百种笔迹写出来
Canvas 指纹是这套体系里最经典的一环,实现简单得让人不安:
ini
function getCanvasFingerprint() {
const canvas = document.createElement('canvas');
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('Cwm fjordbank glyphs vext quiz 😃', 2, 15);
// 这串 base64 就是原始指纹,再做一次 hash 即可
return canvas.toDataURL();
}
同样一段代码,同样一句话,为什么能区分不同的人?
因为渲染这件事从来不是标准化的 。你的 GPU 型号、显卡驱动版本、操作系统的字体渲染引擎(Windows 的 DirectWrite、macOS 的 CoreText、Linux 的 FreeType)、抗锯齿策略、甚至那个 emoji 用的是哪套字体------这些叠加起来,会导致亚像素级别的渲染差异。
肉眼完全看不出区别,两张图看着一模一样。但 hash 出来天差地别,就像让一百个人抄写同一句话:内容完全相同,笔迹各不相同。
WebGL 更直接,很多环境下能直接读到类似这样的字符串:
scss
ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0, D3D11)
四、这技术到底用在哪
值得说清楚的是,指纹技术本身是中性的,而且防御侧的用量远大于追踪侧:
- 支付风控:判断这次交易的设备是不是你常用的那台设备
- 反爬虫 / 反薅羊毛:如果同一台机器批量注册一千个账号,指纹会暴露
- 广告归因:跨站追踪用户行为,做兴趣画像
- 账号安全:异地异设备登录时触发二次验证
前两个和最后一个,多数人会认为是正当的;第三个则是隐私争议的核心------它绕过了用户对 Cookie 的控制权,你既没法拒绝,也无从察觉。
于是就有了指纹浏览器 (Anti-detect Browser)这类工具:它们通常基于 Chromium 修改内核,给每个环境伪造一套自洽且稳定的特征,配合独立 IP 使用。这是个典型的猫鼠游戏 ------ 而且鼠这边并不好过,因为伪装的破绽往往不在单个字段,而在字段之间的矛盾:UA 声称是 macOS,字体列表里却躺着微软雅黑;时区是 Asia/Shanghai,出口 IP 却在法兰克福。检测方甚至不需要知道你的真实指纹,只要判断出"这个指纹不像真的"就够了。
五、普通人能做什么
坦率讲,选择不多,而且都有代价:
- Tor Browser 走的是"让所有人长得一样"的路子------统一窗口尺寸、禁用 Canvas 读取、限制字体。代价是体验妥协。
- Brave / Firefox 提供指纹随机化,每次给出略有不同的结果,让追踪者拼不出稳定 ID。
- Safari 会主动向网站提供一个简化版的系统配置信息。
至于装一堆插件 ------ 需要注意:插件本身也是指纹的一部分。有人为了藏起来做的努力,有时反而让他在人群中更扎眼。
六、写在最后
作为开发者,canvas.getContext('2d') 我们可能写过无数次,但很少想过它还能这么用。
浏览器指纹是个很好的提醒:Web 平台上几乎每一个为了"让开发者做事更方便"而暴露的接口,都同时是一个信息泄露的通道。 硬件并发数是为了让你合理分配 Worker,WebGL 参数是为了让你做性能降级,字体探测是为了让你做排版兜底------它们的设计初衷都无可指摘,组合起来却成了一套精确的身份识别系统。
能力与暴露,从来是同一枚硬币的两面。
如果对具体的检测与对抗细节感兴趣,可以看看开源库 FingerprintJS,它把这套采集逻辑写得非常清楚。