浏览器指纹(Browser Fingerprinting)
浏览器指纹是一种识别和跟踪用户的技术,通过收集用户浏览器和设备的各种特征来创建一个独特的标识符。以下是实现浏览器指纹的一些常见方法:
-
用户代理字符串(User Agent String)
用户代理字符串包含浏览器名称、版本、操作系统类型和版本等信息。虽然这一信息比较容易伪造,但结合其他特征可以增加指纹的唯一性。 -
浏览器插件和扩展(Browser Plugins and Extensions)
不同用户安装的插件和扩展通常不同。通过检测浏览器中安装的插件列表,可以增加指纹的唯一性。 -
屏幕分辨率和颜色深度(Screen Resolution and Color Depth)
屏幕分辨率和颜色深度是与用户设备硬件相关的参数,这些参数在不同设备上通常不同。 -
HTTP 标头(HTTP Headers)
浏览器发送的 HTTP 请求头中包含了很多信息,例如 Accept-Language、Accept-Encoding、Referer 等,可以用来进一步区分用户。 -
浏览器设置(Browser Settings)
例如启用或禁用 cookies、Do Not Track 设置、是否允许 JavaScript、是否启用广告拦截等,这些设置通常因用户而异。 -
字体(Fonts)
检测系统和浏览器中安装的字体。不同系统和用户安装的字体可能不同,通过字体列表可以增加唯一性。 -
Canvas 指纹(Canvas Fingerprinting)
利用 HTML5 Canvas 元素绘制一个图形,不同的浏览器、操作系统、显卡、配置等绘制出的图形会略有不同。这种微小的差异可以用于指纹识别。 -
WebGL 指纹(WebGL Fingerprinting)
类似于 Canvas 指纹,WebGL 指纹通过绘制复杂的 3D 图形来检测显卡和驱动的差异。 -
时区和语言(Timezone and Language)
用户所在的时区和浏览器的语言设置也可以作为指纹的一部分。 -
媒体设备(Media Devices)
获取用户的音频、视频输入输出设备信息,如麦克风和摄像头的列表。
示例
以下是一个简单的示例,收集部分浏览器指纹信息:
function getBrowserFingerprint() {
let fingerprint = '';
// User Agent
fingerprint += navigator.userAgent;
// Screen Resolution
fingerprint += screen.width + 'x' + screen.height + screen.colorDepth;
// Plugins
fingerprint += Array.from(navigator.plugins).map(plugin => plugin.name).join(',');
// Timezone
fingerprint += Intl.DateTimeFormat().resolvedOptions().timeZone;
// Language
fingerprint += navigator.language;
// Canvas Fingerprint
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser Fingerprint', 2, 2);
fingerprint += canvas.toDataURL();
return md5(fingerprint);
}
console.log(getBrowserFingerprint()); // '4ce1fad627cec0fa2ad0495bf3ec6189'
准确率
可以通过更加细致和深入的特征收集来提高识别率例如:
- Canvas 指纹和 WebGL 指纹
function getCanvasFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser Fingerprint', 2, 2);
return canvas.toDataURL();
}
function getWebGLFingerprint() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) return null;
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
return vendor + '|' + renderer;
}
- 音频指纹(Audio Fingerprinting)
function getAudioFingerprint() {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const analyser = audioCtx.createAnalyser();
const gain = audioCtx.createGain();
const scriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1);
let fingerprint = '';
scriptProcessor.onaudioprocess = function(event) {
const array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
fingerprint = array.join('');
audioCtx.close();
};
oscillator.type = 'triangle';
oscillator.connect(analyser);
analyser.connect(scriptProcessor);
scriptProcessor.connect(gain);
gain.connect(audioCtx.destination);
oscillator.start(0);
return fingerprint;
}
- 硬件并行性(Hardware Concurrency)
function getHardwareConcurrency() {
return navigator.hardwareConcurrency;
}
- 内存信息
function getDeviceMemory() {
return navigator.deviceMemory;
}
- 电池状态
async function getBatteryStatus() {
const battery = await navigator.getBattery();
return battery.level + '|' + battery.charging;
}
- 传感器数据
利用设备传感器数据,如加速度计、陀螺仪等
function getSensorData() {
let sensorData = '';
if ('DeviceOrientationEvent' in window) {
window.addEventListener('deviceorientation', (event) => {
sensorData = event.alpha + '|' + event.beta + '|' + event.gamma;
});
}
return sensorData;
}
- 更细致的浏览器和操作系统信息
即使是相同型号的手机和相同版本的浏览器,浏览器的具体配置和环境也可能略有不同。例如浏览器缓存、历史记录、Cookie 等。
function getDetailedBrowserInfo() {
return {
userAgent: navigator.userAgent,
appVersion: navigator.appVersion,
platform: navigator.platform,
languages: navigator.languages,
cookieEnabled: navigator.cookieEnabled,
javaEnabled: navigator.javaEnabled(),
doNotTrack: navigator.doNotTrack,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
};
}
- WebRTC 本地 IP
function getLocalIPs(callback) {
const ips = [];
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
if (!RTCPeerConnection) return;
const pc = new RTCPeerConnection({ iceServers: [] });
pc.createDataChannel('');
pc.onicecandidate = function(e) {
if (!e.candidate) { // All ICE candidates have been delivered
callback(ips);
return;
}
const ip = e.candidate.candidate.split(' ')[4];
if (!ips.includes(ip)) ips.push(ip);
};
pc.createOffer().then(offer => pc.setLocalDescription(offer)).catch(err => console.error(err));
}
getLocalIPs((ips) => {
console.log(ips.join('|')); // 输出ip
});
9.时间戳
利用用户访问的精确时间戳进行区分。即使两个人同时访问网站,精确到毫秒的时间戳也会有所不同。
10.服务端去重
利用用户上报的特征值进行筛选去重,最终获得更为精确的匿名id返回给客户端作为唯一标识。
考虑到动态变化对指纹稳定性的影响。可以考虑将动态特征和静态特征分开处理,动态特征可以作为辅助信息,给不同特征分配不同的权重,同时设定一个阈值来判断特征变化是否在可接受范围内
不同的设置可能会产生独特的结果,从而更精确地识别其用户
确定特征权重:
- User Agent: 10%
- Screen Resolution: 5%
- Plugins: 10%
- Timezone: 5%
- Language: 5%
- Canvas Fingerprint: 15%
- WebGL Fingerprint: 15%
- Audio Fingerprint: 15%
- Network Information: 10%
- Local IP via WebRTC: 10%
from flask import Flask, request, jsonify
import hashlib
import sqlite3
app = Flask(__name__)
# 创建数据库连接
def get_db_connection():
conn = sqlite3.connect('fingerprints.db')
conn.row_factory = sqlite3.Row
return conn
# 创建表
def create_table():
conn = get_db_connection()
conn.execute('''
CREATE TABLE IF NOT EXISTS fingerprints (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_agent TEXT,
screen_resolution TEXT,
plugins TEXT,
timezone TEXT,
language TEXT,
canvas_fingerprint TEXT,
webgl_fingerprint TEXT,
audio_fingerprint TEXT,
network_info TEXT,
local_ip TEXT,
anonymous_id TEXT
)
''')
conn.commit()
conn.close()
# 计算特征相似度
def calculate_similarity(fingerprint1, fingerprint2, weights):
score = 0
total_weight = sum(weights.values())
for key in weights:
if fingerprint1.get(key) == fingerprint2.get(key):
score += weights[key]
return score / total_weight
@app.route('/api/fingerprint', methods=['POST'])
def fingerprint():
data = request.json
fingerprint = {
'user_agent': data.get('user_agent'),
'screen_resolution': data.get('screen_resolution'),
'plugins': data.get('plugins'),
'timezone': data.get('timezone'),
'language': data.get('language'),
'canvas_fingerprint': data.get('canvas_fingerprint'),
'webgl_fingerprint': data.get('webgl_fingerprint'),
'audio_fingerprint': data.get('audio_fingerprint'),
'network_info': data.get('network_info'),
'local_ip': data.get('local_ip')
}
weights = {
'user_agent': 0.1,
'screen_resolution': 0.05,
'plugins': 0.1,
'timezone': 0.05,
'language': 0.05,
'canvas_fingerprint': 0.15,
'webgl_fingerprint': 0.15,
'audio_fingerprint': 0.15,
'network_info': 0.1,
'local_ip': 0.1
}
conn = get_db_connection()
cursor = conn.execute('SELECT * FROM fingerprints')
rows = cursor.fetchall()
matched_id = None
highest_score = 0
threshold = 0.7 # 设置一个阈值,超过此阈值则认为是同一用户
for row in rows:
db_fingerprint = {
'user_agent': row['user_agent'],
'screen_resolution': row['screen_resolution'],
'plugins': row['plugins'],
'timezone': row['timezone'],
'language': row['language'],
'canvas_fingerprint': row['canvas_fingerprint'],
'webgl_fingerprint': row['webgl_fingerprint'],
'audio_fingerprint': row['audio_fingerprint'],
'network_info': row['network_info'],
'local_ip': row['local_ip']
}
score = calculate_similarity(fingerprint, db_fingerprint, weights)
if score > highest_score:
highest_score = score
matched_id = row['anonymous_id']
if highest_score >= threshold:
anonymous_id = matched_id
else:
anonymous_id = hashlib.md5((str(fingerprint) + str(highest_score)).encode()).hexdigest()
conn.execute('INSERT INTO fingerprints (user_agent, screen_resolution, plugins, timezone, language, canvas_fingerprint, webgl_fingerprint, audio_fingerprint, network_info, local_ip, anonymous_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
(fingerprint['user_agent'], fingerprint['screen_resolution'], fingerprint['plugins'], fingerprint['timezone'], fingerprint['language'], fingerprint['canvas_fingerprint'], fingerprint['webgl_fingerprint'], fingerprint['audio_fingerprint'], fingerprint['network_info'], fingerprint['local_ip'], anonymous_id))
conn.commit()
conn.close()
return jsonify({'anonymousId': anonymous_id})
if __name__ == '__main__':
create_table()
app.run()