Initial commit
Dieser Commit ist enthalten in:
356
infrastructure/services/fingerprint/fingerprint_rotation_service.py
Normale Datei
356
infrastructure/services/fingerprint/fingerprint_rotation_service.py
Normale Datei
@ -0,0 +1,356 @@
|
||||
"""
|
||||
Fingerprint Rotation Service - Handles fingerprint rotation and modification.
|
||||
"""
|
||||
|
||||
import random
|
||||
import copy
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
|
||||
from domain.entities.browser_fingerprint import (
|
||||
BrowserFingerprint, CanvasNoise, WebRTCConfig,
|
||||
HardwareConfig, NavigatorProperties
|
||||
)
|
||||
|
||||
|
||||
class RotationStrategy(Enum):
|
||||
"""Fingerprint rotation strategies."""
|
||||
MINIMAL = "minimal" # Only rotate most volatile attributes
|
||||
GRADUAL = "gradual" # Gradual changes over time
|
||||
COMPLETE = "complete" # Complete regeneration (new device)
|
||||
|
||||
|
||||
class FingerprintRotationService:
|
||||
"""Service for rotating and modifying fingerprints."""
|
||||
|
||||
def __init__(self):
|
||||
self.rotation_history = {} # Track rotation history per fingerprint
|
||||
|
||||
def rotate_fingerprint(self,
|
||||
fingerprint: BrowserFingerprint,
|
||||
strategy: RotationStrategy = RotationStrategy.MINIMAL) -> BrowserFingerprint:
|
||||
"""Rotate a fingerprint based on the specified strategy."""
|
||||
|
||||
# Create a deep copy to avoid modifying the original
|
||||
rotated = self._deep_copy_fingerprint(fingerprint)
|
||||
|
||||
# Track rotation
|
||||
self._track_rotation(fingerprint.fingerprint_id, strategy)
|
||||
|
||||
# Apply rotation based on strategy
|
||||
if strategy == RotationStrategy.MINIMAL:
|
||||
self._apply_minimal_rotation(rotated)
|
||||
elif strategy == RotationStrategy.GRADUAL:
|
||||
self._apply_gradual_rotation(rotated)
|
||||
elif strategy == RotationStrategy.COMPLETE:
|
||||
self._apply_complete_rotation(rotated)
|
||||
|
||||
# Update rotation timestamp
|
||||
rotated.last_rotated = datetime.now()
|
||||
|
||||
return rotated
|
||||
|
||||
def _apply_minimal_rotation(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Apply minimal rotation - only most volatile attributes."""
|
||||
|
||||
# Rotate canvas noise seed (most commonly changed)
|
||||
fingerprint.canvas_noise.seed = random.randint(1000, 99999)
|
||||
|
||||
# Slight audio context variations
|
||||
fingerprint.audio_context_base_latency += random.uniform(-0.001, 0.001)
|
||||
fingerprint.audio_context_output_latency += random.uniform(-0.001, 0.001)
|
||||
|
||||
# Update timezone offset if DST might have changed
|
||||
if self._should_update_dst(fingerprint):
|
||||
fingerprint.timezone_offset += 60 if fingerprint.timezone_offset > 0 else -60
|
||||
|
||||
# Minor font list changes (add/remove 1-2 fonts)
|
||||
self._rotate_fonts_minimal(fingerprint)
|
||||
|
||||
def _apply_gradual_rotation(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Apply gradual rotation - simulate natural changes over time."""
|
||||
|
||||
# All minimal changes
|
||||
self._apply_minimal_rotation(fingerprint)
|
||||
|
||||
# WebGL renderer might get driver updates
|
||||
if random.random() < 0.3:
|
||||
self._update_webgl_version(fingerprint)
|
||||
|
||||
# Browser version update
|
||||
if random.random() < 0.4:
|
||||
self._update_browser_version(fingerprint)
|
||||
|
||||
# Screen resolution might change (external monitor)
|
||||
if random.random() < 0.1:
|
||||
self._rotate_screen_resolution(fingerprint)
|
||||
|
||||
# More significant font changes
|
||||
self._rotate_fonts_gradual(fingerprint)
|
||||
|
||||
def _apply_complete_rotation(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Apply complete rotation - simulate device change."""
|
||||
|
||||
# Keep only static components if account-bound
|
||||
if fingerprint.account_bound and fingerprint.static_components:
|
||||
# Maintain same device class but change specifics
|
||||
self._rotate_within_device_class(fingerprint)
|
||||
else:
|
||||
# Complete change - new device simulation
|
||||
self._rotate_to_new_device(fingerprint)
|
||||
|
||||
# New canvas noise configuration
|
||||
fingerprint.canvas_noise = CanvasNoise(
|
||||
noise_level=random.choice([0.01, 0.02, 0.03]),
|
||||
seed=random.randint(1000, 99999),
|
||||
algorithm=random.choice(["gaussian", "uniform"])
|
||||
)
|
||||
|
||||
# New audio context
|
||||
fingerprint.audio_context_base_latency = random.uniform(0.005, 0.02)
|
||||
fingerprint.audio_context_output_latency = random.uniform(0.01, 0.04)
|
||||
|
||||
# Complete font list regeneration
|
||||
self._rotate_fonts_complete(fingerprint)
|
||||
|
||||
def _rotate_fonts_minimal(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Minimal font rotation - add/remove 1-2 fonts."""
|
||||
current_fonts = fingerprint.font_list.copy()
|
||||
|
||||
# Remove 1-2 random fonts
|
||||
if len(current_fonts) > 10:
|
||||
for _ in range(random.randint(0, 2)):
|
||||
if current_fonts:
|
||||
current_fonts.remove(random.choice(current_fonts))
|
||||
|
||||
# Add 1-2 new fonts
|
||||
possible_additions = ["Segoe UI Light", "Segoe UI Semibold", "Arial Narrow",
|
||||
"Century Gothic", "Franklin Gothic Medium"]
|
||||
for _ in range(random.randint(0, 2)):
|
||||
new_font = random.choice(possible_additions)
|
||||
if new_font not in current_fonts:
|
||||
current_fonts.append(new_font)
|
||||
|
||||
fingerprint.font_list = current_fonts
|
||||
|
||||
def _rotate_fonts_gradual(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Gradual font rotation - change 20-30% of fonts."""
|
||||
current_fonts = fingerprint.font_list.copy()
|
||||
num_to_change = int(len(current_fonts) * random.uniform(0.2, 0.3))
|
||||
|
||||
# Remove some fonts
|
||||
for _ in range(num_to_change // 2):
|
||||
if current_fonts:
|
||||
current_fonts.remove(random.choice(current_fonts))
|
||||
|
||||
# Add new fonts
|
||||
base_fonts = self._get_base_fonts_for_platform(fingerprint.navigator_props.platform)
|
||||
for _ in range(num_to_change // 2):
|
||||
available = [f for f in base_fonts if f not in current_fonts]
|
||||
if available:
|
||||
current_fonts.append(random.choice(available))
|
||||
|
||||
fingerprint.font_list = current_fonts
|
||||
|
||||
def _rotate_fonts_complete(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Complete font rotation - regenerate font list."""
|
||||
base_fonts = self._get_base_fonts_for_platform(fingerprint.navigator_props.platform)
|
||||
num_fonts = random.randint(int(len(base_fonts) * 0.7), int(len(base_fonts) * 0.9))
|
||||
fingerprint.font_list = random.sample(base_fonts, num_fonts)
|
||||
|
||||
def _update_webgl_version(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Update WebGL renderer version (driver update)."""
|
||||
renderer = fingerprint.webgl_renderer
|
||||
|
||||
# Update version numbers in renderer string
|
||||
if "ANGLE" in renderer:
|
||||
# Update Direct3D version
|
||||
if "Direct3D11" in renderer:
|
||||
renderer = renderer.replace("Direct3D11", "Direct3D11.1")
|
||||
elif "Direct3D9" in renderer:
|
||||
renderer = renderer.replace("Direct3D9", "Direct3D11")
|
||||
|
||||
# Update driver versions
|
||||
import re
|
||||
version_pattern = r'\d+\.\d+\.\d+\.\d+'
|
||||
match = re.search(version_pattern, renderer)
|
||||
if match:
|
||||
old_version = match.group()
|
||||
parts = old_version.split('.')
|
||||
# Increment minor version
|
||||
parts[2] = str(int(parts[2]) + random.randint(1, 10))
|
||||
new_version = '.'.join(parts)
|
||||
renderer = renderer.replace(old_version, new_version)
|
||||
|
||||
fingerprint.webgl_renderer = renderer
|
||||
|
||||
def _update_browser_version(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Update browser version in user agent."""
|
||||
user_agent = fingerprint.navigator_props.user_agent
|
||||
|
||||
# Update Chrome version
|
||||
if "Chrome/" in user_agent:
|
||||
import re
|
||||
match = re.search(r'Chrome/(\d+)\.', user_agent)
|
||||
if match:
|
||||
current_version = int(match.group(1))
|
||||
new_version = current_version + random.randint(1, 3)
|
||||
user_agent = user_agent.replace(f'Chrome/{current_version}', f'Chrome/{new_version}')
|
||||
fingerprint.navigator_props.user_agent = user_agent
|
||||
|
||||
def _rotate_screen_resolution(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Rotate screen resolution (external monitor change)."""
|
||||
common_resolutions = [
|
||||
(1920, 1080), (2560, 1440), (3840, 2160), # 16:9
|
||||
(1920, 1200), (2560, 1600), # 16:10
|
||||
(1366, 768), (1600, 900) # Laptop
|
||||
]
|
||||
|
||||
current = fingerprint.hardware_config.screen_resolution
|
||||
available = [res for res in common_resolutions if res != current]
|
||||
|
||||
if available:
|
||||
fingerprint.hardware_config.screen_resolution = random.choice(available)
|
||||
|
||||
def _rotate_within_device_class(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Rotate within the same device class (for account-bound fingerprints)."""
|
||||
static = fingerprint.static_components
|
||||
|
||||
if static.device_type == "desktop":
|
||||
# Change to different desktop configuration
|
||||
fingerprint.hardware_config.hardware_concurrency = random.choice([4, 8, 12, 16])
|
||||
fingerprint.hardware_config.device_memory = random.choice([8, 16, 32])
|
||||
elif static.device_type == "mobile":
|
||||
# Change to different mobile configuration
|
||||
fingerprint.hardware_config.hardware_concurrency = random.choice([4, 6, 8])
|
||||
fingerprint.hardware_config.device_memory = random.choice([3, 4, 6])
|
||||
|
||||
# Update renderer within same GPU vendor
|
||||
if "Intel" in static.gpu_vendor:
|
||||
fingerprint.webgl_renderer = random.choice([
|
||||
"ANGLE (Intel HD Graphics 620)",
|
||||
"ANGLE (Intel UHD Graphics 630)",
|
||||
"ANGLE (Intel Iris Xe Graphics)"
|
||||
])
|
||||
elif "NVIDIA" in static.gpu_vendor:
|
||||
fingerprint.webgl_renderer = random.choice([
|
||||
"ANGLE (NVIDIA GeForce GTX 1060)",
|
||||
"ANGLE (NVIDIA GeForce RTX 3060)",
|
||||
"ANGLE (NVIDIA GeForce GTX 1660)"
|
||||
])
|
||||
|
||||
def _rotate_to_new_device(self, fingerprint: BrowserFingerprint) -> None:
|
||||
"""Rotate to completely new device."""
|
||||
# This would typically regenerate most components
|
||||
# For now, we'll do significant changes
|
||||
|
||||
# New hardware configuration
|
||||
fingerprint.hardware_config = HardwareConfig(
|
||||
hardware_concurrency=random.choice([4, 8, 12, 16]),
|
||||
device_memory=random.choice([4, 8, 16, 32]),
|
||||
max_touch_points=0,
|
||||
screen_resolution=random.choice([(1920, 1080), (2560, 1440)]),
|
||||
color_depth=random.choice([24, 32]),
|
||||
pixel_ratio=random.choice([1.0, 1.5, 2.0])
|
||||
)
|
||||
|
||||
# New WebGL
|
||||
vendors = ["Intel Inc.", "NVIDIA Corporation", "AMD"]
|
||||
fingerprint.webgl_vendor = random.choice(vendors)
|
||||
|
||||
if fingerprint.webgl_vendor == "Intel Inc.":
|
||||
fingerprint.webgl_renderer = "ANGLE (Intel HD Graphics)"
|
||||
elif fingerprint.webgl_vendor == "NVIDIA Corporation":
|
||||
fingerprint.webgl_renderer = "ANGLE (NVIDIA GeForce GTX)"
|
||||
else:
|
||||
fingerprint.webgl_renderer = "ANGLE (AMD Radeon)"
|
||||
|
||||
def _should_update_dst(self, fingerprint: BrowserFingerprint) -> bool:
|
||||
"""Check if DST update might be needed."""
|
||||
# Simple check - in reality would check actual DST dates
|
||||
if fingerprint.last_rotated:
|
||||
days_since_rotation = (datetime.now() - fingerprint.last_rotated).days
|
||||
# DST changes roughly every 6 months
|
||||
return days_since_rotation > 180
|
||||
return False
|
||||
|
||||
def _get_base_fonts_for_platform(self, platform: str) -> List[str]:
|
||||
"""Get base fonts for platform."""
|
||||
if "Win" in platform:
|
||||
return ["Arial", "Times New Roman", "Verdana", "Tahoma", "Segoe UI",
|
||||
"Calibri", "Consolas", "Georgia", "Impact", "Comic Sans MS"]
|
||||
elif "Mac" in platform:
|
||||
return ["Arial", "Helvetica", "Times New Roman", "Georgia",
|
||||
"Verdana", "Monaco", "Courier", "Geneva", "Futura"]
|
||||
else:
|
||||
return ["Arial", "Times New Roman", "Liberation Sans", "DejaVu Sans",
|
||||
"Ubuntu", "Droid Sans", "Noto Sans"]
|
||||
|
||||
def _track_rotation(self, fingerprint_id: str, strategy: RotationStrategy) -> None:
|
||||
"""Track rotation history."""
|
||||
if fingerprint_id not in self.rotation_history:
|
||||
self.rotation_history[fingerprint_id] = []
|
||||
|
||||
self.rotation_history[fingerprint_id].append({
|
||||
"timestamp": datetime.now(),
|
||||
"strategy": strategy.value
|
||||
})
|
||||
|
||||
# Keep only last 100 rotations
|
||||
self.rotation_history[fingerprint_id] = self.rotation_history[fingerprint_id][-100:]
|
||||
|
||||
def _deep_copy_fingerprint(self, fingerprint: BrowserFingerprint) -> BrowserFingerprint:
|
||||
"""Create a deep copy of a fingerprint."""
|
||||
# Manual deep copy to ensure all nested objects are copied
|
||||
return BrowserFingerprint(
|
||||
fingerprint_id=fingerprint.fingerprint_id,
|
||||
canvas_noise=CanvasNoise(
|
||||
noise_level=fingerprint.canvas_noise.noise_level,
|
||||
seed=fingerprint.canvas_noise.seed,
|
||||
algorithm=fingerprint.canvas_noise.algorithm
|
||||
),
|
||||
webrtc_config=WebRTCConfig(
|
||||
enabled=fingerprint.webrtc_config.enabled,
|
||||
ice_servers=fingerprint.webrtc_config.ice_servers.copy(),
|
||||
local_ip_mask=fingerprint.webrtc_config.local_ip_mask,
|
||||
disable_webrtc=fingerprint.webrtc_config.disable_webrtc
|
||||
),
|
||||
font_list=fingerprint.font_list.copy(),
|
||||
hardware_config=HardwareConfig(
|
||||
hardware_concurrency=fingerprint.hardware_config.hardware_concurrency,
|
||||
device_memory=fingerprint.hardware_config.device_memory,
|
||||
max_touch_points=fingerprint.hardware_config.max_touch_points,
|
||||
screen_resolution=fingerprint.hardware_config.screen_resolution,
|
||||
color_depth=fingerprint.hardware_config.color_depth,
|
||||
pixel_ratio=fingerprint.hardware_config.pixel_ratio
|
||||
),
|
||||
navigator_props=NavigatorProperties(
|
||||
platform=fingerprint.navigator_props.platform,
|
||||
vendor=fingerprint.navigator_props.vendor,
|
||||
vendor_sub=fingerprint.navigator_props.vendor_sub,
|
||||
product=fingerprint.navigator_props.product,
|
||||
product_sub=fingerprint.navigator_props.product_sub,
|
||||
app_name=fingerprint.navigator_props.app_name,
|
||||
app_version=fingerprint.navigator_props.app_version,
|
||||
user_agent=fingerprint.navigator_props.user_agent,
|
||||
language=fingerprint.navigator_props.language,
|
||||
languages=fingerprint.navigator_props.languages.copy() if fingerprint.navigator_props.languages else [],
|
||||
online=fingerprint.navigator_props.online,
|
||||
do_not_track=fingerprint.navigator_props.do_not_track
|
||||
),
|
||||
webgl_vendor=fingerprint.webgl_vendor,
|
||||
webgl_renderer=fingerprint.webgl_renderer,
|
||||
audio_context_base_latency=fingerprint.audio_context_base_latency,
|
||||
audio_context_output_latency=fingerprint.audio_context_output_latency,
|
||||
audio_context_sample_rate=fingerprint.audio_context_sample_rate,
|
||||
timezone=fingerprint.timezone,
|
||||
timezone_offset=fingerprint.timezone_offset,
|
||||
plugins=fingerprint.plugins.copy() if fingerprint.plugins else [],
|
||||
created_at=fingerprint.created_at,
|
||||
last_rotated=fingerprint.last_rotated,
|
||||
static_components=fingerprint.static_components, # This is immutable
|
||||
rotation_seed=fingerprint.rotation_seed,
|
||||
account_bound=fingerprint.account_bound,
|
||||
platform_specific_config=fingerprint.platform_specific_config.copy() if fingerprint.platform_specific_config else {}
|
||||
)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren