Initial commit
Dieser Commit ist enthalten in:
217
infrastructure/services/fingerprint/account_fingerprint_service.py
Normale Datei
217
infrastructure/services/fingerprint/account_fingerprint_service.py
Normale Datei
@ -0,0 +1,217 @@
|
||||
"""
|
||||
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)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren