跳到主要内容

浏览器指纹

问题

什么是浏览器指纹?它是如何工作的?如何防护?

答案

浏览器指纹(Browser Fingerprinting)是一种无需 Cookie 的用户追踪技术,通过收集浏览器和设备的各种特征信息,生成一个唯一标识符来识别用户。

指纹采集原理

基础指纹信息

采集示例

interface BasicFingerprint {
userAgent: string;
language: string;
languages: string[];
platform: string;
hardwareConcurrency: number;
deviceMemory?: number;
screenResolution: [number, number];
colorDepth: number;
timezone: string;
timezoneOffset: number;
cookieEnabled: boolean;
doNotTrack: string | null;
plugins: string[];
touchSupport: {
maxTouchPoints: number;
touchEvent: boolean;
touchStart: boolean;
};
}

function getBasicFingerprint(): BasicFingerprint {
return {
// 浏览器信息
userAgent: navigator.userAgent,
language: navigator.language,
languages: [...navigator.languages],
platform: navigator.platform,

// 硬件信息
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: (navigator as any).deviceMemory,

// 屏幕信息
screenResolution: [screen.width, screen.height],
colorDepth: screen.colorDepth,

// 时区
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timezoneOffset: new Date().getTimezoneOffset(),

// 功能支持
cookieEnabled: navigator.cookieEnabled,
doNotTrack: navigator.doNotTrack,

// 插件(仅部分浏览器支持)
plugins: Array.from(navigator.plugins).map(p => p.name),

// 触控支持
touchSupport: {
maxTouchPoints: navigator.maxTouchPoints,
touchEvent: 'TouchEvent' in window,
touchStart: 'ontouchstart' in window,
},
};
}

Canvas 指纹

Canvas 指纹利用不同设备渲染图像的微小差异来生成唯一标识。

原理

实现

function getCanvasFingerprint(): string {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

if (!ctx) return '';

canvas.width = 200;
canvas.height = 50;

// 绘制文本
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillStyle = '#f60';
ctx.fillRect(0, 0, 100, 50);

ctx.fillStyle = '#069';
ctx.fillText('Hello, Canvas!', 2, 15);

ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
ctx.fillText('Fingerprint', 4, 35);

// 绘制图形
ctx.beginPath();
ctx.arc(50, 25, 10, 0, Math.PI * 2);
ctx.fillStyle = '#f0f';
ctx.fill();

// 提取数据并哈希
const dataURL = canvas.toDataURL();
return hashString(dataURL);
}

// 简单哈希函数
function hashString(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(16);
}

更精确的 Canvas 指纹

async function getDetailedCanvasFingerprint(): Promise<string> {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

if (!ctx) return '';

canvas.width = 256;
canvas.height = 128;

// 1. 渐变
const gradient = ctx.createLinearGradient(0, 0, 256, 0);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(0.5, '#00ff00');
gradient.addColorStop(1, '#0000ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 64);

// 2. 特殊字符(emoji 渲染差异大)
ctx.font = '30px Arial, sans-serif';
ctx.fillStyle = '#000';
ctx.fillText('🎨🔒👤', 10, 100);

// 3. 数学函数渲染
ctx.font = '12px serif';
ctx.fillText('∫∞≈√π', 150, 100);

// 4. 阴影效果
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillRect(180, 80, 50, 30);

// 提取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// 使用 SubtleCrypto 生成哈希
const hashBuffer = await crypto.subtle.digest(
'SHA-256',
imageData.data
);

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

WebGL 指纹

WebGL 指纹利用 GPU 和驱动程序信息来识别设备。

interface WebGLFingerprint {
renderer: string;
vendor: string;
version: string;
shadingLanguageVersion: string;
maxTextureSize: number;
extensions: string[];
}

function getWebGLFingerprint(): WebGLFingerprint | null {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

if (!gl) return null;

// 获取调试信息扩展
const debugInfo = (gl as WebGLRenderingContext).getExtension('WEBGL_debug_renderer_info');

return {
// GPU 信息(通过调试扩展)
renderer: debugInfo
? (gl as WebGLRenderingContext).getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: 'Not available',
vendor: debugInfo
? (gl as WebGLRenderingContext).getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)
: 'Not available',

// WebGL 版本
version: (gl as WebGLRenderingContext).getParameter((gl as WebGLRenderingContext).VERSION),
shadingLanguageVersion: (gl as WebGLRenderingContext).getParameter(
(gl as WebGLRenderingContext).SHADING_LANGUAGE_VERSION
),

// 硬件能力
maxTextureSize: (gl as WebGLRenderingContext).getParameter(
(gl as WebGLRenderingContext).MAX_TEXTURE_SIZE
),

// 支持的扩展
extensions: (gl as WebGLRenderingContext).getSupportedExtensions() || [],
};
}

WebGL 渲染指纹

function getWebGLRenderFingerprint(): string {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;

const gl = canvas.getContext('webgl');
if (!gl) return '';

// 创建着色器程序
const vertexShaderSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;

const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(gl_FragCoord.x / 256.0, gl_FragCoord.y / 256.0, 0.5, 1.0);
}
`;

// 编译着色器并渲染...
// 提取渲染结果生成指纹

const pixels = new Uint8Array(256 * 256 * 4);
gl.readPixels(0, 0, 256, 256, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

return hashString(Array.from(pixels).join(''));
}

Audio 指纹

通过 Web Audio API 的音频处理差异生成指纹。

async function getAudioFingerprint(): Promise<string> {
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const analyser = audioContext.createAnalyser();
const compressor = audioContext.createDynamicsCompressor();

// 设置参数
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(10000, audioContext.currentTime);

// 配置压缩器
compressor.threshold.setValueAtTime(-50, audioContext.currentTime);
compressor.knee.setValueAtTime(40, audioContext.currentTime);
compressor.ratio.setValueAtTime(12, audioContext.currentTime);
compressor.attack.setValueAtTime(0, audioContext.currentTime);
compressor.release.setValueAtTime(0.25, audioContext.currentTime);

// 连接节点
oscillator.connect(compressor);
compressor.connect(analyser);
analyser.connect(audioContext.destination);

oscillator.start(0);

// 获取频率数据
const frequencyData = new Uint8Array(analyser.frequencyBinCount);
await new Promise(resolve => setTimeout(resolve, 100));
analyser.getByteFrequencyData(frequencyData);

oscillator.stop();
await audioContext.close();

// 生成指纹
return hashString(Array.from(frequencyData).join(''));
}

字体指纹

检测系统安装的字体来识别设备。

function getFontFingerprint(): string[] {
const baseFonts = ['monospace', 'sans-serif', 'serif'];
const testFonts = [
'Arial', 'Verdana', 'Helvetica', 'Times New Roman', 'Georgia',
'Courier New', 'Comic Sans MS', 'Impact', 'Trebuchet MS', 'Palatino',
'Lucida Console', 'Monaco', 'Consolas', 'Menlo',
// 中文字体
'Microsoft YaHei', 'SimHei', 'SimSun', 'PingFang SC', 'Heiti SC',
];

const testString = 'mmmmmmmmmmlli';
const testSize = '72px';

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return [];

// 获取基准字体宽度
const baseWidths: Record<string, number> = {};
baseFonts.forEach(baseFont => {
ctx.font = `${testSize} ${baseFont}`;
baseWidths[baseFont] = ctx.measureText(testString).width;
});

// 检测已安装字体
const detectedFonts: string[] = [];

testFonts.forEach(font => {
let detected = false;

for (const baseFont of baseFonts) {
ctx.font = `${testSize} '${font}', ${baseFont}`;
const width = ctx.measureText(testString).width;

if (width !== baseWidths[baseFont]) {
detected = true;
break;
}
}

if (detected) {
detectedFonts.push(font);
}
});

return detectedFonts;
}

完整指纹生成

interface BrowserFingerprint {
basic: BasicFingerprint;
canvas: string;
webgl: WebGLFingerprint | null;
audio: string;
fonts: string[];
hash: string;
}

async function generateFingerprint(): Promise<BrowserFingerprint> {
const basic = getBasicFingerprint();
const canvas = getCanvasFingerprint();
const webgl = getWebGLFingerprint();
const audio = await getAudioFingerprint();
const fonts = getFontFingerprint();

// 组合所有数据生成最终哈希
const combinedData = JSON.stringify({
basic, canvas, webgl, audio, fonts
});

const hashBuffer = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(combinedData)
);

const hash = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');

return { basic, canvas, webgl, audio, fonts, hash };
}

// 使用
const fingerprint = await generateFingerprint();
console.log('浏览器指纹:', fingerprint.hash);

指纹库 FingerprintJS

// 使用 FingerprintJS 库
import FingerprintJS from '@aspect/fingerprintjs';

async function getVisitorId(): Promise<string> {
const fp = await FingerprintJS.load();
const result = await fp.get();

console.log('访客 ID:', result.visitorId);
console.log('置信度:', result.confidence.score);
console.log('组件:', result.components);

return result.visitorId;
}

防护措施

用户端防护

// 1. 使用隐私保护浏览器(Tor、Brave)

// 2. 浏览器扩展
// - Canvas Blocker
// - WebGL Fingerprint Defender
// - Font Fingerprint Defender

// 3. Firefox 隐私设置
// privacy.resistFingerprinting = true

开发者防护

// 1. 添加随机噪声
function addCanvasNoise(canvas: HTMLCanvasElement): void {
const ctx = canvas.getContext('2d');
if (!ctx) return;

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;

// 添加随机噪声
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.max(0, Math.min(255, data[i] + Math.random() * 2 - 1));
data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + Math.random() * 2 - 1));
data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + Math.random() * 2 - 1));
}

ctx.putImageData(imageData, 0, 0);
}

// 2. 代理 Canvas API
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(...args) {
addCanvasNoise(this);
return originalToDataURL.apply(this, args);
};

常见面试问题

Q1: 什么是浏览器指纹?有什么用途?

答案

浏览器指纹是通过收集浏览器和设备特征生成的唯一标识符。

用途

  • 反欺诈:检测恶意用户
  • 广告追踪:无 Cookie 追踪
  • 安全验证:辅助身份认证
  • 数据分析:访客统计

采集维度

  • 基础信息(UA、语言、时区)
  • Canvas/WebGL 渲染
  • 音频处理
  • 字体列表
  • 硬件信息

Q2: Canvas 指纹的原理是什么?

答案

Canvas 指纹利用不同设备渲染图像的微小差异

  1. 使用 Canvas 绘制文本和图形
  2. 提取像素数据
  3. 生成哈希值

差异来源

  • GPU 型号和驱动
  • 字体渲染引擎
  • 抗锯齿算法
  • 操作系统
const ctx = canvas.getContext('2d');
ctx.fillText('Hello', 10, 10);
const dataURL = canvas.toDataURL(); // 不同设备结果不同

Q3: 如何对抗浏览器指纹追踪?

答案

方法效果说明
隐私浏览器Tor、Brave 等
浏览器扩展Canvas Blocker 等
Firefox 设置resistFingerprinting
VPN只能隐藏 IP

核心思路:让指纹更通用或随机化:

  • 返回通用值(所有用户一样)
  • 添加随机噪声(每次不同)

答案

特性浏览器指纹Cookie
存储无需存储浏览器存储
清除无法清除用户可删除
隐私模式仍可追踪无效
跨浏览器可能相同不共享
准确性可能重复唯一
稳定性可能变化固定

Q5: 指纹识别的准确率能达到多少?

答案

根据研究,组合多种指纹技术可达 90%+ 的唯一识别率

  • Canvas 指纹:~90% 唯一性
  • WebGL 指纹:结合 Canvas 提高准确性
  • 音频指纹:额外维度
  • 多维度组合:可达 99%+ 准确率

影响因素

  • 浏览器市场份额
  • 设备多样性
  • 用户配置差异

相关链接