""" Account Fingerprint Service - Manages account-bound fingerprints. """ import hashlib import random from typing import Optional, Dict, Any from datetime import datetime, timedelta from domain.entities.browser_fingerprint import BrowserFingerprint from .fingerprint_generator_service import FingerprintGeneratorService from .fingerprint_rotation_service import FingerprintRotationService, RotationStrategy class AccountFingerprintService: """Service for managing account-bound fingerprints.""" def __init__(self, generator_service: Optional[FingerprintGeneratorService] = None, rotation_service: Optional[FingerprintRotationService] = None): self.generator_service = generator_service or FingerprintGeneratorService() self.rotation_service = rotation_service or FingerprintRotationService() def generate_account_fingerprint(self, account_id: str, platform: str, proxy_location: Optional[str] = None) -> BrowserFingerprint: """Generate a fingerprint bound to a specific account.""" # Generate base fingerprint with account binding fingerprint = self.generator_service.generate_fingerprint( platform=platform, proxy_location=proxy_location, account_id=account_id ) # Apply deterministic variations based on account ID self._apply_account_variations(fingerprint, account_id) return fingerprint def get_daily_fingerprint(self, base_fingerprint: BrowserFingerprint, account_id: str) -> BrowserFingerprint: """Get deterministic daily variation of account fingerprint.""" if not base_fingerprint.account_bound: raise ValueError("Fingerprint must be account-bound for daily variations") # Calculate days since creation days_since_creation = (datetime.now() - base_fingerprint.created_at).days # Generate deterministic seed for today today_seed = self._generate_daily_seed(account_id, days_since_creation) # Apply deterministic variations varied = self._apply_daily_variations(base_fingerprint, today_seed) return varied def _apply_account_variations(self, fingerprint: BrowserFingerprint, account_id: str) -> None: """Apply account-specific variations to fingerprint.""" # Use account ID to seed variations account_hash = int(hashlib.md5(account_id.encode()).hexdigest()[:8], 16) # Deterministic but unique variations random.seed(account_hash) # Vary canvas noise seed within range base_seed = fingerprint.canvas_noise.seed fingerprint.canvas_noise.seed = base_seed + (account_hash % 1000) # Vary audio latencies slightly fingerprint.audio_context_base_latency += (account_hash % 10) * 0.0001 fingerprint.audio_context_output_latency += (account_hash % 10) * 0.0002 # Select subset of fonts deterministically if len(fingerprint.font_list) > 5: num_to_remove = account_hash % 3 + 1 for _ in range(num_to_remove): fingerprint.font_list.pop(random.randint(0, len(fingerprint.font_list) - 1)) # Reset random seed random.seed() def _generate_daily_seed(self, account_id: str, day_number: int) -> int: """Generate deterministic seed for a specific day.""" # Combine account ID with day number seed_string = f"{account_id}:{day_number}" seed_hash = hashlib.sha256(seed_string.encode()).hexdigest() # Convert to integer seed return int(seed_hash[:8], 16) def _apply_daily_variations(self, fingerprint: BrowserFingerprint, daily_seed: int) -> BrowserFingerprint: """Apply deterministic daily variations.""" # Use rotation service with controlled randomness original_seed = random.getstate() random.seed(daily_seed) # Minimal rotation for daily changes varied = self.rotation_service.rotate_fingerprint(fingerprint, RotationStrategy.MINIMAL) # Additional deterministic changes self._apply_time_based_changes(varied, daily_seed) # Restore original random state random.setstate(original_seed) return varied def _apply_time_based_changes(self, fingerprint: BrowserFingerprint, seed: int) -> None: """Apply time-based changes that would naturally occur.""" # Browser version might update weekly week_number = seed % 52 if week_number % 4 == 0: # Every 4 weeks self._increment_browser_version(fingerprint) # System uptime affects audio latency hour_of_day = datetime.now().hour fingerprint.audio_context_base_latency += (hour_of_day / 24) * 0.001 # Network conditions affect WebRTC if seed % 3 == 0: # Change local IP mask (different network) fingerprint.webrtc_config.local_ip_mask = f"192.168.{seed % 255}.x" def _increment_browser_version(self, fingerprint: BrowserFingerprint) -> None: """Increment browser version number.""" import re user_agent = fingerprint.navigator_props.user_agent # Find Chrome version match = re.search(r'Chrome/(\d+)\.(\d+)\.(\d+)\.(\d+)', user_agent) if match: major = int(match.group(1)) minor = int(match.group(2)) build = int(match.group(3)) patch = int(match.group(4)) # Increment build number build += 1 # Update user agent old_version = match.group(0) new_version = f"Chrome/{major}.{minor}.{build}.{patch}" fingerprint.navigator_props.user_agent = user_agent.replace(old_version, new_version) def validate_account_binding(self, fingerprint: BrowserFingerprint, account_id: str) -> bool: """Validate that a fingerprint is properly bound to an account.""" if not fingerprint.account_bound: return False if not fingerprint.static_components: return False if not fingerprint.rotation_seed: return False # Could add more validation here (e.g., check against database) return True def get_fingerprint_age_days(self, fingerprint: BrowserFingerprint) -> int: """Get age of fingerprint in days.""" if not fingerprint.created_at: return 0 return (datetime.now() - fingerprint.created_at).days def should_rotate_fingerprint(self, fingerprint: BrowserFingerprint) -> bool: """Determine if fingerprint should be rotated.""" age_days = self.get_fingerprint_age_days(fingerprint) # Rotate after 30 days if age_days > 30: return True # Check last rotation if fingerprint.last_rotated: days_since_rotation = (datetime.now() - fingerprint.last_rotated).days if days_since_rotation > 7: # Weekly rotation check return True return False def prepare_session_fingerprint(self, fingerprint: BrowserFingerprint, session_data: Dict[str, Any]) -> BrowserFingerprint: """Prepare fingerprint for use with existing session.""" # Sessions might have slightly different characteristics session_fp = self._deep_copy_fingerprint(fingerprint) # Apply session-specific adjustments if "browser_version" in session_data: self._update_to_browser_version(session_fp, session_data["browser_version"]) if "screen_resolution" in session_data: session_fp.hardware_config.screen_resolution = tuple(session_data["screen_resolution"]) return session_fp def _update_to_browser_version(self, fingerprint: BrowserFingerprint, version: str) -> None: """Update fingerprint to specific browser version.""" import re user_agent = fingerprint.navigator_props.user_agent # Replace Chrome version user_agent = re.sub(r'Chrome/[\d.]+', f'Chrome/{version}', user_agent) fingerprint.navigator_props.user_agent = user_agent # Update app version app_version = fingerprint.navigator_props.app_version app_version = re.sub(r'Chrome/[\d.]+', f'Chrome/{version}', app_version) fingerprint.navigator_props.app_version = app_version def _deep_copy_fingerprint(self, fingerprint: BrowserFingerprint) -> BrowserFingerprint: """Create a deep copy of fingerprint.""" # Delegate to rotation service's implementation return self.rotation_service._deep_copy_fingerprint(fingerprint)