JavaScript 实现浏览器指纹生成:基础字段、Canvas 采样与 SHA-256 摘要

浏览器指纹不是浏览器内部的固定 ID,而是根据浏览器、操作系统和设备环境中的多个特征,组合计算出来的一种摘要结果。

本文使用原生 JavaScript 实现一个简化版本,适合用于前端实验、浏览器兼容性测试、安全课程演示和隐私研究。不涉及绕过平台检测或伪造浏览器环境。

一、浏览器指纹生成流程

基本流程如下:

复制代码
采集公开特征
    ↓
统一数据格式
    ↓
计算哈希摘要
    ↓
多次测试稳定性

常见特征包括:

  • User-Agent
  • 浏览器语言
  • 时区
  • 屏幕尺寸
  • 设备像素比
  • 硬件并发数
  • Cookie 是否启用
  • Canvas 渲染结果
  • WebGL 能力信息

MDN 将浏览器指纹描述为:网站收集浏览器和底层操作系统的区别性特征,并组合这些特征识别浏览环境。MDN:Fingerprinting

二、采集基础浏览器特征

复制代码
function collectBasicFeatures() {
  return {
    userAgent: navigator.userAgent,
    language: navigator.language,
    languages: Array.from(navigator.languages || []),
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    platform: navigator.platform || '',
    screenWidth: screen.width,
    screenHeight: screen.height,
    colorDepth: screen.colorDepth,
    pixelRatio: window.devicePixelRatio,
    hardwareConcurrency: navigator.hardwareConcurrency || null,
    cookieEnabled: navigator.cookieEnabled
  };
}

console.log(collectBasicFeatures());

这些字段主要覆盖:

  • 浏览器和系统环境
  • 语言与时区
  • 屏幕显示参数
  • 部分硬件信息
  • Cookie 状态

单个字段通常不具备很高的区分能力,多个字段组合后才可能形成更有区分度的环境描述。

三、统一数据格式

为了避免字段顺序、空值和数组格式影响结果,需要对特征进行规范化处理:

复制代码
function normalizeFeatures(features) {
  return [
    features.userAgent,
    features.language,
    features.languages.join(','),
    features.timezone,
    features.platform,
    features.screenWidth,
    features.screenHeight,
    features.colorDepth,
    features.pixelRatio,
    features.hardwareConcurrency,
    features.cookieEnabled
  ]
    .map(value => String(value ?? ''))
    .join('|');
}

使用方法:

复制代码
const features = collectBasicFeatures();
const normalized = normalizeFeatures(features);

console.log(normalized);

只要输入字段保持一致,规范化后的字符串就应该保持一致。

四、使用 SHA-256 生成摘要

浏览器可以通过 Web Crypto API 计算 SHA-256:

复制代码
async function sha256(text) {
  const data = new TextEncoder().encode(text);
  const buffer = await crypto.subtle.digest('SHA-256', data);

  return Array.from(new Uint8Array(buffer))
    .map(byte => byte.toString(16).padStart(2, '0'))
    .join('');
}

SubtleCrypto.digest() 会将可变长度数据转换成固定长度摘要,通常要求页面运行在 HTTPS 或 localhost 等安全上下文中。MDN:SubtleCrypto.digest()

五、生成 Canvas 特征

Canvas 指纹的基本思路是绘制固定内容,再读取绘制结果:

复制代码
function getCanvasSource() {
  const canvas = document.createElement('canvas');

  canvas.width = 320;
  canvas.height = 100;

  const ctx = canvas.getContext('2d');

  ctx.textBaseline = 'top';
  ctx.font = '18px Arial';
  ctx.fillStyle = '#245';
  ctx.fillText('browser-fingerprint-test', 10, 10);

  ctx.fillStyle = 'rgba(80, 160, 220, 0.7)';
  ctx.beginPath();
  ctx.arc(200, 50, 28, 0, Math.PI * 2);
  ctx.fill();

  return canvas.toDataURL();
}

Canvas 的输出可能受到操作系统、字体、浏览器渲染引擎和图形环境影响。MDN:CanvasRenderingContext2D

由于浏览器可能限制 Canvas 读取,所以代码中应允许它失败:

复制代码
function tryGetCanvasSource() {
  try {
    return getCanvasSource();
  } catch (error) {
    console.warn('Canvas 特征不可用:', error);
    return null;
  }
}

Canvas 只能作为辅助信号,不能单独代表设备身份。

六、完整示例

复制代码
async function buildFingerprint() {
  const basicFeatures = collectBasicFeatures();
  const normalizedBasic = normalizeFeatures(basicFeatures);
  const canvasSource = tryGetCanvasSource();

  const payload = JSON.stringify({
    basic: normalizedBasic,
    canvas: canvasSource
  });

  const fingerprint = await sha256(payload);

  return {
    fingerprint,
    basicFeatures,
    canvasAvailable: Boolean(canvasSource)
  };
}

buildFingerprint().then(result => {
  console.log('浏览器指纹:', result.fingerprint);
  console.log('基础特征:', result.basicFeatures);
  console.log('Canvas 是否可用:', result.canvasAvailable);
});

如果要在页面中显示结果,可以使用:

复制代码
<pre id="output">正在生成...</pre>

buildFingerprint().then(result => {
  document.querySelector('#output').textContent =
    JSON.stringify(result, null, 2);
});

七、如何测试指纹稳定性?

建议按照下面的顺序进行测试:

复制代码
首次打开页面
    ↓
刷新页面
    ↓
重启浏览器
    ↓
清除 Cookie
    ↓
打开无痕窗口
    ↓
修改屏幕分辨率
    ↓
升级浏览器后再次测试

可以记录以下结果:

测试条件 指纹摘要 Canvas 状态
普通窗口 记录结果 可用/不可用
页面刷新后 记录结果 可用/不可用
浏览器重启后 记录结果 可用/不可用
无痕窗口 记录结果 可用/不可用
修改分辨率后 记录结果 可用/不可用

需要区分两个概念:

  • 稳定性:同一个环境在不同时间是否保持相近。
  • 区分度:不同环境之间是否容易区分。

稳定并不等于唯一。浏览器升级、系统更新和隐私策略变化,都可能导致摘要改变。

八、常见问题排查

1. 每次生成结果都不同

可能原因:

  • Canvas 读取受到隐私保护;
  • 输入数据中包含动态值;
  • 屏幕或窗口尺寸发生变化;
  • 字段顺序不固定;
  • 浏览器环境发生了变化。

可以先打印中间数据:

复制代码
console.log(normalizedBasic);
console.log(canvasSource);

先判断是特征采集变化,还是哈希计算变化。

2. crypto.subtle 不可用

优先使用:

复制代码
http://localhost

或通过 HTTPS 访问页面。

3. 不同设备生成了相同结果

这不一定是代码错误。基础字段较少时,不同设备可能拥有相同或相近的环境组合。

这是正常现象。Cookie 和浏览器指纹属于不同机制,清除 Cookie 不一定会改变浏览器环境特征。

九、生产环境使用建议

如果这类代码用于正式项目,建议:

  1. 只采集完成业务目标所需的字段。
  2. 不默认请求位置、摄像头和麦克风权限。
  3. 不长期保存完整原始特征。
  4. 不把哈希摘要当作身份认证凭证。
  5. 不使用指纹作为唯一封禁依据。
  6. 在隐私政策中说明采集目的和保存周期。
  7. 评估不同浏览器的兼容性和误判情况。

如果需要管理多套相互隔离的浏览环境,也可以将 Web4 Browser 作为环境管理工具案例进行了解,但仍应自行核对 Profile 隔离、数据处理和权限边界。

十、隐私与合规边界

浏览器厂商正在通过减少可暴露信息、统一部分参数和限制接口访问等方式降低指纹识别风险。W3C:Mitigating Browser Fingerprinting

开发者需要注意:

  • 哈希处理不等于匿名化;
  • 浏览器指纹不等于真实身份;
  • 指纹不一定永久稳定;
  • 不应在用户不知情的情况下长期跟踪;
  • 高影响业务决策不应只依赖浏览器指纹。

十一、总结

浏览器指纹生成可以概括为:

复制代码
采集浏览器环境
    ↓
规范化特征数据
    ↓
生成 Canvas 等辅助特征
    ↓
使用 SHA-256 计算摘要
    ↓
通过多轮测试验证稳定性

它适合用于前端实验、兼容性分析、安全研究和隐私测试,但不是绝对唯一的设备编号,也不能替代账号认证、行为分析或人工审核。

正式项目还需要综合考虑浏览器兼容性、误判率、数据保存、用户告知和隐私合规要求。

相关推荐
不可能片场16 分钟前
Electron 子进程退化成 node 的 env 陷阱
前端·electron
凡泰极客科技17 分钟前
凡泰极客亮相CIFS金融峰会,加速金融超级App建设,推动AI进入业务层
人工智能·金融
搬砖记录员20 分钟前
录屏总黑屏?90% 的人没关这个开关——浏览器硬件加速冲突排查指南
前端·音视频开发
GoGeekBaird23 分钟前
我给 DeepSeek Harness 造了个铸造台
后端
Anita-lee23 分钟前
跨境在线旅行社(OTA)国际化运营中的突发服务保障与海外用户体验管理
大数据·人工智能·ux
YOU OU23 分钟前
Spring Cloud概述
后端·spring·spring cloud
promiseThen24 分钟前
我用 7 条铁律管住 Cursor:让 AI 写代码不再「自由发挥」
前端·ai编程
深海鱼在掘金30 分钟前
深入浅出RAG——第9章:高级检索策略
人工智能
Forerror202632 分钟前
API网关怎么选?MAI Gateway入选2026年企业级AI网关推荐清单
人工智能·ai工具·maigateway·企业ai网关·企业级大模型网关