怎么通过浏览器指纹识别采集程序?
浏览器指纹采集程序通过前端读取导航器、屏幕、硬件、时区等40余项属性并异步采集Canvas、WebGL、Audio,拼接后经SHA-256哈希生成设备ID,再由服务端TLS/HTTP特征二次校验;单一字段区分度低,须全量组合哈希。
浏览器指纹识别采集的核心只有三步:在页面里用 JavaScript 读 40 项以上稳定的浏览器与设备属性,把它们拼成字符串做一次哈希得到设备 ID,再在服务端用 TLS/HTTP 层特征做二次校验。纯前端就能拿到大部分可用熵,剩下那部分必须靠服务端补齐——这是所有成熟采集方案的通用骨架。
浏览器指纹识别到底采集哪些字段?
结论:可用字段分四类,导航器属性、屏幕与图形、硬件能力、时区与行为,前三类是主力。导航器侧能直接读到的有 navigator.userAgent、navigator.language、navigator.languages、navigator.platform、navigator.hardwareConcurrency(CPU 逻辑核心数)、navigator.deviceMemory(内存 GB 数,Chrome 上限报 8)、navigator.maxTouchPoints(触摸点数)。屏幕侧是 screen.width/height、screen.colorDepth、window.devicePixelRatio。图形侧是 Canvas 和 WebGL,硬件侧是 GPU 型号和音频栈,时区侧是 Intl.DateTimeFormat().resolvedOptions().timeZone 与 new Date().getTimezoneOffset()。
单一字段几乎没有区分度。结论:UA 字符串单独只有约 10 bit 熵,而且用户装个插件就能改;把 40 多个字段串起来之后,同一台设备在几天内重复命中的概率才会显著上升。所以采集程序的写法一定是「全量读 + 组合哈希」,不是挑一两个字段。
采集代码怎么写?
步骤就四步:建对象读同步字段、异步跑 Canvas/WebGL/Audio、拼字符串、哈希入库。同步字段要一次性读完,因为部分浏览器对 navigator 的访问有节流。
async function collect() {
const fp = {
ua: navigator.userAgent,
lang: navigator.language,
langs: (navigator.languages || []).join(','),
platform: navigator.platform,
cores: navigator.hardwareConcurrency,
mem: navigator.deviceMemory || 0,
touch: navigator.maxTouchPoints,
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
tzOffset: new Date().getTimezoneOffset(),
screen: [screen.width, screen.height, screen.colorDepth, devicePixelRatio].join('x'),
canvas: canvasHash(),
webgl: webglInfo(),
audio: await audioHash()
};
return sha256(JSON.stringify(fp)).slice(0, 16);
}
哈希用 SHA-256 取前 16 位十六进制是社区最常见的做法,碰撞概率够低,存储也省。注意把对象 key 顺序固定,否则同一设备会算出两个 ID。
Canvas 和 WebGL 指纹怎么算?
结论:Canvas 指纹靠的是同一段绘制指令在不同 GPU、驱动、字体集下得到的像素结果不同。做法是画一段带文字的图形再 toDataURL() 取 base64,比较末尾片段即可。
function canvasHash() {
const c = document.createElement('canvas');
c.width = 240; c.height = 60;
const ctx = c.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(0, 0, 120, 30);
ctx.fillStyle = '#069';
ctx.fillText('Fingerprint 指纹', 2, 15);
return c.toDataURL().slice(-64);
}
WebGL 更直接,读扩展里的真实 GPU 型号:
const gl = document.createElement('canvas').getContext('webgl');
const dbg = gl.getExtension('WEBGL_debug_renderer_info');
gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL); // "Google Inc. (NVIDIA)"
gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL); // "ANGLE (NVIDIA, ...)"
Firefox 和 Safari 会对 UNMASKED_RENDERER_WEBGL 做脱敏,返回通用字符串,这时要退回到 gl.getParameter(gl.VERSION) 加支持的扩展列表。音频指纹则用 OfflineAudioContext 跑 OscillatorNode 接到 DynamicsCompressorNode,对输出 buffer 求和取特征值,不同设备的浮点舍入差异会体现出来。字体枚举可以用 document.fonts.check('12px "某字体"') 逐个判定。
Chromium 的 Client Hints 能用吗?
结论:可以用,但不能当唯一来源。Chromium 内核从 89 版起提供 navigator.userAgentData,高熵字段必须主动申请:
await navigator.userAgentData.getHighEntropyValues(
['platform','platformVersion','architecture','model','bitness','uaFullVersion']
);
这个接口只在 HTTPS 下可用,Safari 和 Firefox 至今未实现,所以它只能作为 UA 的补充维度,不能替代原来的 navigator.userAgent。
服务端还要补什么?
结论:TLS 指纹和 HTTP/2 指纹是最难伪造的一层。JA3 是把 ClientHello 里的 TLS 版本、加密套件列表、扩展列表按固定顺序拼成字符串再取 MD5;2023 年 FoxIO 提出的 JA4 改成了可读格式并支持 TLS 1.3,目前新项目基本都在往 JA4 迁。再叠加 HTTP/2 的 SETTINGS 参数指纹(业界称 Akamai fingerprint)和请求头顺序,这层特征改起来要动底层网络栈,普通脚本和爬虫很难伪装一致。前端哈希和服务端指纹不一致时,基本可以判定是模拟环境。
采集时有哪些坑?
合规是第一条。结论:设备指纹在中国《个人信息保护法》(2021 年 11 月 1 日施行)和欧盟 GDPR 下通常被认定为个人信息,必须告知并取得同意,还要提供关闭开关,做风控和做画像的法律要求不一样。
第二条是分层比对。把字段拆成稳定层(时区、平台、GPU 型号、核心数)和易变层(浏览器小版本、窗口尺寸、缩放比),命中判断时只用稳定层,否则浏览器一升级就会把老用户认成新设备。
第三条是反制环境。Brave 用 farbling 给 Canvas 和 Audio 加随机噪声,同一台机器每次读出不同值;Firefox 的 privacy.resistFingerprinting 会把时区统一成 UTC、UA 固定成通用值;Safari 的 ITP 限制存储可用性。遇到这些环境,指纹稳定性会明显下降,采集程序必须对噪声值做多次采样取众数,或者直接降级到其他识别手段。
整套流程记住一句话:前端负责广度,服务端负责可信度,稳定层负责长期命中,三者缺一不可。
转载请注明出处,版权归原作者所有。