Browser Fingerprint Viewer — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Browser Fingerprint — pure logic (Java port): the 18-signal privacy-risk
// registry with canonical grouping, risk counts, and a stable SHA-256
// fingerprint hash.
//
// Language: Java 17 — standard library only (java.security for SHA-256,
// java.util.HexFormat for hex output). Save as BrowserFingerprint.java.
// Source: CosmoDev polyglot showcase port; canonical = src/lib/browser-fingerprint.ts
// + this tool's python.py / rust.rs.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Ported from the TypeScript reference. 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.
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
public final class BrowserFingerprint {
/** Privacy-risk classification. */
public enum RiskLevel {
/** Coarse; shared by millions of browsers. */
LOW("low"),
/** Narrows you to a sizable-but-specific population. */
MEDIUM("medium"),
/** Near-unique on its own or part of a rare combination. */
HIGH("high");
private final String label;
RiskLevel(String label) {
this.label = label;
}
public String label() {
return label;
}
}
/** Groups related signals. */
public enum FingerprintCategory {
HARDWARE("Hardware"),
GRAPHICS("Graphics"),
NETWORK("Network"),
BROWSER("Browser"),
INPUT("Input");
private final String label;
FingerprintCategory(String label) {
this.label = label;
}
public String label() {
return label;
}
}
/** One collected, classified signal. */
public record FingerprintSignal(String id, String label, String value, RiskLevel risk,
FingerprintCategory category) {
}
/** Classifies one signal id. */
private record SignalDef(String label, RiskLevel risk, FingerprintCategory category) {
}
/** One category and its signals. */
public record CategoryGroup(FingerprintCategory category, List<FingerprintSignal> signals) {
}
/** Signals per risk level (drives the summary line). */
public record RiskCounts(int low, int medium, int high) {
}
/** Canonical category order (card grid, top to bottom). */
private static final FingerprintCategory[] FINGERPRINT_CATEGORIES = {
FingerprintCategory.HARDWARE,
FingerprintCategory.GRAPHICS,
FingerprintCategory.NETWORK,
FingerprintCategory.BROWSER,
FingerprintCategory.INPUT,
};
/**
* 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 static final Map<String, SignalDef> SIGNAL_DEFINITIONS = Map.ofEntries(
// High risk
Map.entry("canvas", new SignalDef("Canvas fingerprint", RiskLevel.HIGH, FingerprintCategory.GRAPHICS)),
Map.entry("webgl-renderer", new SignalDef("WebGL renderer", RiskLevel.HIGH, FingerprintCategory.GRAPHICS)),
Map.entry("webgl-vendor", new SignalDef("WebGL vendor", RiskLevel.HIGH, FingerprintCategory.GRAPHICS)),
Map.entry("fonts", new SignalDef("Installed fonts", RiskLevel.HIGH, FingerprintCategory.BROWSER)),
Map.entry("timezone", new SignalDef("Timezone", RiskLevel.HIGH, FingerprintCategory.BROWSER)),
// Medium risk
Map.entry("screen", new SignalDef("Screen & color depth", RiskLevel.MEDIUM, FingerprintCategory.HARDWARE)),
Map.entry("device-pixel-ratio", new SignalDef("Device pixel ratio", RiskLevel.MEDIUM, FingerprintCategory.HARDWARE)),
Map.entry("hardware-concurrency", new SignalDef("CPU cores", RiskLevel.MEDIUM, FingerprintCategory.HARDWARE)),
Map.entry("device-memory", new SignalDef("Device Memory", RiskLevel.MEDIUM, FingerprintCategory.HARDWARE)),
Map.entry("platform", new SignalDef("Platform", RiskLevel.MEDIUM, FingerprintCategory.BROWSER)),
Map.entry("languages", new SignalDef("Languages", RiskLevel.MEDIUM, FingerprintCategory.BROWSER)),
Map.entry("touch", new SignalDef("Touch support", RiskLevel.MEDIUM, FingerprintCategory.INPUT)),
Map.entry("connection", new SignalDef("Connection type", RiskLevel.MEDIUM, FingerprintCategory.NETWORK)),
// Low risk
Map.entry("user-agent", new SignalDef("User agent", RiskLevel.LOW, FingerprintCategory.BROWSER)),
Map.entry("do-not-track", new SignalDef("Do Not Track", RiskLevel.LOW, FingerprintCategory.BROWSER)),
Map.entry("cookies-enabled", new SignalDef("Cookies enabled", RiskLevel.LOW, FingerprintCategory.BROWSER)),
Map.entry("online", new SignalDef("Online status", RiskLevel.LOW, FingerprintCategory.NETWORK)),
Map.entry("pdf-viewer", new SignalDef("PDF viewer", RiskLevel.LOW, FingerprintCategory.BROWSER)));
/** Risk level for a known signal id; null for unknown ids. */
public static RiskLevel classifySignalRisk(String id) {
SignalDef def = SIGNAL_DEFINITIONS.get(id);
return def == null ? null : def.risk();
}
/**
* Build a signal from its id and collected value. Throws
* IllegalArgumentException on unknown ids so a typo'd id fails loudly
* instead of silently rendering an unclassified row.
*/
public static FingerprintSignal buildSignal(String id, String value) {
SignalDef def = SIGNAL_DEFINITIONS.get(id);
if (def == null) {
throw new IllegalArgumentException("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 List<CategoryGroup> groupByCategory(List<FingerprintSignal> signals) {
List<CategoryGroup> groups = new ArrayList<>();
for (FingerprintCategory category : FINGERPRINT_CATEGORIES) {
List<FingerprintSignal> members = new ArrayList<>();
for (FingerprintSignal signal : signals) {
if (signal.category() == category) {
members.add(signal);
}
}
if (!members.isEmpty()) {
groups.add(new CategoryGroup(category, members));
}
}
return groups;
}
/** Count signals per risk level. */
public static RiskCounts countByRisk(List<FingerprintSignal> signals) {
int low = 0;
int medium = 0;
int high = 0;
for (FingerprintSignal signal : signals) {
switch (signal.risk()) {
case LOW -> low++;
case MEDIUM -> medium++;
case HIGH -> high++;
}
}
return new RiskCounts(low, medium, high);
}
/**
* The exact byte string the fingerprint hash is computed over: every value
* joined with '|' in signal order.
*/
public static String concatSignalValues(List<FingerprintSignal> signals) {
StringBuilder joined = new StringBuilder();
for (FingerprintSignal signal : signals) {
if (joined.length() > 0) {
joined.append('|');
}
joined.append(signal.value());
}
return joined.toString();
}
/** SHA-256 hex digest of the UTF-8 bytes of input. */
public static String sha256Hex(String input) {
try {
byte[] digest = MessageDigest.getInstance("SHA-256")
.digest(input.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(digest);
} catch (NoSuchAlgorithmException e) {
// Every Java platform ships SHA-256; unreachable in practice.
throw new IllegalStateException("SHA-256 unavailable", e);
}
}
/**
* 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 String hashFingerprint(List<FingerprintSignal> signals) {
return sha256Hex(concatSignalValues(signals));
}
public static void main(String[] args) {
List<FingerprintSignal> signals = List.of(
buildSignal("timezone", "Europe/Paris"),
buildSignal("screen", "2560×1440 @ 24-bit"),
buildSignal("user-agent", "Mozilla/5.0 (Macintosh)"));
for (CategoryGroup group : groupByCategory(signals)) {
System.out.println("[" + group.category().label() + "]");
for (FingerprintSignal signal : group.signals()) {
System.out.println(" " + signal.label() + ": " + signal.value()
+ " (" + signal.risk().label() + " risk)");
}
}
RiskCounts counts = countByRisk(signals);
System.out.println("risk mix: " + counts.high() + " high / " + counts.medium()
+ " medium / " + counts.low() + " low");
System.out.println("fingerprint: " + hashFingerprint(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 →