Files
AccountForger-neuerUpload/infrastructure/services/fingerprint/fingerprint_rotation_service.py
Claude Project Manager 04585e95b6 Initial commit
2025-08-01 23:50:28 +02:00

356 Zeilen
16 KiB
Python

"""
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 {}
)