anomalyScore > 70.0; } /** * Check if fingerprint is highly suspicious */ public function isHighlySuspicious(): bool { return $this->anomalyScore > 90.0; } /** * Get anomaly score */ public function getAnomalyScore(): float { return $this->anomalyScore; } /** * Get anomalous features */ public function getAnomalousFeatures(): array { return $this->anomalousFeatures; } /** * Check if fingerprint matches another */ public function matches(self $other): bool { return $this->fingerprintHash === $other->fingerprintHash; } /** * Calculate similarity with another fingerprint */ public function similarity(self $other): float { $commonFeatures = array_intersect_key($this->features, $other->features); $totalFeatures = array_merge($this->features, $other->features); if (empty($totalFeatures)) { return 0.0; } $matchingFeatures = 0; foreach ($commonFeatures as $key => $value) { if ($this->features[$key] === $other->features[$key]) { $matchingFeatures++; } } return ($matchingFeatures / count($totalFeatures)) * 100; } /** * Extract features from raw data */ private static function extractFeatures(array $data): array { return [ 'user_agent_hash' => hash('sha256', $data['user_agent'] ?? ''), 'language' => $data['accept_language'] ?? null, 'encoding' => $data['accept_encoding'] ?? null, 'canvas_hash' => isset($data['canvas']) ? hash('sha256', json_encode($data['canvas'])) : null, 'webgl_hash' => isset($data['webgl']) ? hash('sha256', json_encode($data['webgl'])) : null, 'audio_hash' => isset($data['audio']) ? hash('sha256', json_encode($data['audio'])) : null, 'font_count' => isset($data['fonts']) ? count($data['fonts']) : null, 'screen_signature' => isset($data['screen']) ? implode('x', $data['screen']) : null, 'timezone_offset' => $data['timezone']['offset'] ?? null, 'plugin_count' => isset($data['plugins']) ? count($data['plugins']) : null, 'touch_support' => $data['touch_support'] ?? false, 'color_depth' => $data['color_depth'] ?? null, 'pixel_ratio' => $data['pixel_ratio'] ?? null, ]; } /** * Detect anomalies in features */ private static function detectAnomalies(array $features): array { $anomalies = []; // Check for common bot indicators if (isset($features['user_agent_hash']) && self::isSuspiciousUserAgent($features['user_agent_hash'])) { $anomalies['suspicious_user_agent'] = 'User agent matches known bot patterns'; } if (isset($features['font_count']) && ($features['font_count'] < 10 || $features['font_count'] > 500)) { $anomalies['unusual_font_count'] = "Unusual font count: {$features['font_count']}"; } if (isset($features['plugin_count']) && $features['plugin_count'] === 0) { $anomalies['no_plugins'] = 'No browser plugins detected'; } if (! isset($features['canvas_hash']) || ! isset($features['webgl_hash'])) { $anomalies['missing_fingerprinting'] = 'Canvas or WebGL fingerprinting blocked'; } if (isset($features['screen_signature'])) { $commonResolutions = ['1920x1080', '1366x768', '1440x900', '1280x720']; if (! in_array($features['screen_signature'], $commonResolutions)) { $anomalies['unusual_resolution'] = "Unusual screen resolution: {$features['screen_signature']}"; } } return $anomalies; } /** * Calculate anomaly score */ private static function calculateAnomalyScore(array $anomalies, array $features): float { $score = 0.0; // Base score from anomaly count $score += count($anomalies) * 20; // Weight specific anomalies $weights = [ 'suspicious_user_agent' => 30, 'unusual_font_count' => 15, 'no_plugins' => 25, 'missing_fingerprinting' => 35, 'unusual_resolution' => 10, ]; foreach ($anomalies as $type => $description) { $score += $weights[$type] ?? 10; } // Cap at 100 return min(100.0, $score); } /** * Check if user agent is suspicious */ private static function isSuspiciousUserAgent(string $userAgentHash): bool { // In a real implementation, this would check against a database of known bot user agents $knownBotHashes = [ // Common bot user agent hashes would be stored here ]; return in_array($userAgentHash, $knownBotHashes); } /** * Generate fingerprint hash */ private static function generateHash(array $features): string { ksort($features); return hash('sha256', json_encode($features)); } /** * Convert to array */ public function toArray(): array { return [ 'fingerprint_hash' => $this->fingerprintHash, 'anomaly_score' => $this->anomalyScore, 'is_anomalous' => $this->isAnomalous(), 'is_highly_suspicious' => $this->isHighlySuspicious(), 'anomalous_features' => $this->anomalousFeatures, 'created_at' => $this->createdAt->toIsoString(), 'user_agent' => $this->userAgent, 'accept_language' => $this->acceptLanguage, 'canvas_available' => $this->canvasFingerprint !== null, 'webgl_available' => $this->webglFingerprint !== null, 'audio_available' => $this->audioFingerprint !== null, 'font_count' => $this->fontList ? count($this->fontList) : null, 'screen_resolution' => $this->screenResolution, 'timezone_info' => $this->timezoneInfo, 'plugin_count' => $this->pluginList ? count($this->pluginList) : null, 'touch_support' => $this->touchSupport, 'color_depth' => $this->colorDepth, 'pixel_ratio' => $this->pixelRatio, ]; } }