Skip to content

Browser Fingerprint Viewer — PHP source

See exactly what websites can learn about your browser without cookies — screen, GPU, fonts, timezone, language, and more. Educational, not tracking.

This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * browser-fingerprint — PHP polyglot showcase port (pure analysis layer).
 *
 * Ported from the TypeScript reference at src/lib/browser-fingerprint.ts.
 * Covers the portable core: the 18-signal registry with privacy-risk
 * classifications, canonical category grouping, risk counts, and the stable
 * SHA-256 fingerprint hash. The browser-API collection layer (canvas / WebGL
 * / font probes) is DOM-bound by design and does not port. Requires PHP 8.1+
 * (backed enums, readonly properties).
 *
 * Display source — part of CosmoDev's polyglot tool pages.
 */

declare(strict_types=1);

namespace CosmoDev\BrowserFingerprint;

/** Privacy-risk classification. */
enum RiskLevel: string
{
    /** Near-unique on its own or part of a rare combination. */
    case High = 'high';
    /** Narrows you to a sizable-but-specific population. */
    case Medium = 'medium';
    /** Coarse; shared by millions of browsers. */
    case Low = 'low';
}

/** Groups related signals. */
enum FingerprintCategory: string
{
    case Hardware = 'Hardware';
    case Graphics = 'Graphics';
    case Network = 'Network';
    case Browser = 'Browser';
    case Input = 'Input';
}

/** One collected, classified signal. */
final class FingerprintSignal
{
    public function __construct(
        public readonly string $id,
        public readonly string $label,
        public readonly string $value,
        public readonly RiskLevel $risk,
        public readonly FingerprintCategory $category,
    ) {
    }
}

/** Classifies one signal id. */
final class SignalDef
{
    public function __construct(
        public readonly string $label,
        public readonly RiskLevel $risk,
        public readonly FingerprintCategory $category,
    ) {
    }
}

/** One category and its signals. */
final class CategoryGroup
{
    /** @param list<FingerprintSignal> $signals */
    public function __construct(
        public readonly FingerprintCategory $category,
        public readonly array $signals,
    ) {
    }
}

/** Signals per risk level (drives the summary line). */
final class RiskCounts
{
    public function __construct(
        public int $low = 0,
        public int $medium = 0,
        public int $high = 0,
    ) {
    }
}

/** Static registry + pure helpers — the portable core of the tool. */
final class BrowserFingerprint
{
    /**
     * Static registry of every signal the tool collects, with its
     * privacy-risk classification. Single source of truth: buildSignal()
     * derives from it, so a signal can never be collected without being
     * classified.
     */
    private const SIGNAL_DEFINITIONS = [
        // High risk
        'canvas' => ['label' => 'Canvas fingerprint', 'risk' => RiskLevel::High, 'category' => FingerprintCategory::Graphics],
        'webgl-renderer' => ['label' => 'WebGL renderer', 'risk' => RiskLevel::High, 'category' => FingerprintCategory::Graphics],
        'webgl-vendor' => ['label' => 'WebGL vendor', 'risk' => RiskLevel::High, 'category' => FingerprintCategory::Graphics],
        'fonts' => ['label' => 'Installed fonts', 'risk' => RiskLevel::High, 'category' => FingerprintCategory::Browser],
        'timezone' => ['label' => 'Timezone', 'risk' => RiskLevel::High, 'category' => FingerprintCategory::Browser],
        // Medium risk
        'screen' => ['label' => 'Screen & color depth', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Hardware],
        'device-pixel-ratio' => ['label' => 'Device pixel ratio', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Hardware],
        'hardware-concurrency' => ['label' => 'CPU cores', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Hardware],
        'device-memory' => ['label' => 'Device Memory', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Hardware],
        'platform' => ['label' => 'Platform', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Browser],
        'languages' => ['label' => 'Languages', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Browser],
        'touch' => ['label' => 'Touch support', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Input],
        'connection' => ['label' => 'Connection type', 'risk' => RiskLevel::Medium, 'category' => FingerprintCategory::Network],
        // Low risk
        'user-agent' => ['label' => 'User agent', 'risk' => RiskLevel::Low, 'category' => FingerprintCategory::Browser],
        'do-not-track' => ['label' => 'Do Not Track', 'risk' => RiskLevel::Low, 'category' => FingerprintCategory::Browser],
        'cookies-enabled' => ['label' => 'Cookies enabled', 'risk' => RiskLevel::Low, 'category' => FingerprintCategory::Browser],
        'online' => ['label' => 'Online status', 'risk' => RiskLevel::Low, 'category' => FingerprintCategory::Network],
        'pdf-viewer' => ['label' => 'PDF viewer', 'risk' => RiskLevel::Low, 'category' => FingerprintCategory::Browser],
    ];

    /** Canonical category order (card grid, top to bottom). */
    private const FINGERPRINT_CATEGORIES = [
        FingerprintCategory::Hardware,
        FingerprintCategory::Graphics,
        FingerprintCategory::Network,
        FingerprintCategory::Browser,
        FingerprintCategory::Input,
    ];

    /** Risk level for a known signal id; null for unknown ids. */
    public static function classifySignalRisk(string $id): ?RiskLevel
    {
        return self::SIGNAL_DEFINITIONS[$id]['risk'] ?? null;
    }

    /**
     * Build a signal from its id and collected value. Throws on unknown ids
     * so a typo'd id fails loudly instead of silently rendering an
     * unclassified row.
     */
    public static function buildSignal(string $id, string $value): FingerprintSignal
    {
        $def = self::SIGNAL_DEFINITIONS[$id] ?? null;
        if ($def === null) {
            throw new \InvalidArgumentException("Unknown fingerprint signal id: {$id}");
        }
        return new FingerprintSignal($id, $def['label'], $value, $def['risk'], $def['category']);
    }

    /** Group signals by category in canonical order, omitting empty categories. */
    public static function groupByCategory(array $signals): array
    {
        $groups = [];
        foreach ($signals as $signal) {
            $groups[$signal->category->value][] = $signal;
        }
        $out = [];
        foreach (self::FINGERPRINT_CATEGORIES as $category) {
            if (isset($groups[$category->value])) {
                $out[] = new CategoryGroup($category, $groups[$category->value]);
            }
        }
        return $out;
    }

    /** Count signals per risk level. */
    public static function countByRisk(array $signals): RiskCounts
    {
        $counts = new RiskCounts();
        foreach ($signals as $signal) {
            match ($signal->risk) {
                RiskLevel::Low => $counts->low++,
                RiskLevel::Medium => $counts->medium++,
                RiskLevel::High => $counts->high++,
            };
        }
        return $counts;
    }

    /** The exact byte string the fingerprint hash is computed over. */
    public static function concatSignalValues(array $signals): string
    {
        return implode('|', array_map(static fn (FingerprintSignal $s): string => $s->value, $signals));
    }

    /** SHA-256 hex digest of input. */
    public static function sha256Hex(string $input): string
    {
        return hash('sha256', $input);
    }

    /**
     * Stable fingerprint ID: SHA-256 over every signal value, joined in
     * signal order. Same browser state -> same hash; any single changed
     * value -> new hash.
     */
    public static function hashFingerprint(array $signals): string
    {
        return self::sha256Hex(self::concatSignalValues($signals));
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →