浏览器指纹
问题
什么是浏览器指纹?它是如何工作的?如何防护?
答案
浏览器指纹(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 指纹利用不同设备渲染图像的微小差异:
- 使用 Canvas 绘制文本和图形
- 提取像素数据
- 生成哈希值
差异来源:
- GPU 型号和驱动
- 字体渲染引擎
- 抗锯齿算法
- 操作系统
const ctx = canvas.getContext('2d');
ctx.fillText('Hello', 10, 10);
const dataURL = canvas.toDataURL(); // 不同设备结果不同
Q3: 如何对抗浏览器指纹追踪?
答案:
| 方法 | 效果 | 说明 |
|---|---|---|
| 隐私浏览器 | 高 | Tor、Brave 等 |
| 浏览器扩展 | 中 | Canvas Blocker 等 |
| Firefox 设置 | 中 | resistFingerprinting |
| VPN | 低 | 只能隐藏 IP |
核心思路:让指纹更通用或随机化:
- 返回通用值(所有用户一样)
- 添加随机噪声(每次不同)
Q4: 浏览器指纹与 Cookie 追踪的区别?
答案:
| 特性 | 浏览器指纹 | Cookie |
|---|---|---|
| 存储 | 无需存储 | 浏览器存储 |
| 清除 | 无法清除 | 用户可删除 |
| 隐私模式 | 仍可追踪 | 无效 |
| 跨浏览器 | 可能相同 | 不共享 |
| 准确性 | 可能重复 | 唯一 |
| 稳定性 | 可能变化 | 固定 |
Q5: 指纹识别的准确率能达到多少?
答案:
根据研究,组合多种指纹技术可达 90%+ 的唯一识别率:
- Canvas 指纹:~90% 唯一性
- WebGL 指纹:结合 Canvas 提高准确性
- 音频指纹:额外维度
- 多维度组合:可达 99%+ 准确率
影响因素:
- 浏览器市场份额
- 设备多样性
- 用户配置差异
相关链接
- FingerprintJS - 商业指纹服务
- AmIUnique - 测试你的浏览器唯一性
- Cover Your Tracks - EFF 指纹测试
- Canvas Blocker - Firefox 扩展