Your fingerprint, measured
Why it matters Fingerprinting sounds abstract until you watch a crowd shrink to one person. In this lab you measure how distinctive each trait is, then see how quickly combining them singles someone out. Nothing you do here leaves the page: the crowd is made up and the code runs in your own browser.
The traits a script can measure
Here are the main traits fingerprinting scripts read, in plain words (checked October 2026; exact details vary by browser):
- Canvas: the page secretly draws some text and shapes, then reads back the pixels. Your graphics chip, drivers and fonts make tiny differences in the result, so the picture becomes a code such as
7d2a. - Fonts: the page measures how wide a word is in many font names. If the width changes, that font is installed. A designer with 300 fonts looks very different from a fresh laptop.
- Screen: width, height, colour depth and pixel density.
- Time zone and language: what your computer is set to, for example
Europe/Londonanden-GB. - GPU: the graphics chip. Through a drawing feature called WebGL, many browsers tell pages a model name.
- Audio: the page processes a short sound without playing it. Small numeric differences in the result depend on your hardware and software.
Try it You can read a few of these yourself, and nothing is sent anywhere. In Chrome, Edge or Firefox press F12 (on a Mac, Cmd+Option+I; in Safari first turn on the Develop menu in Settings, Advanced) and open the Console tab. Type each line and press Enter:
Intl.DateTimeFormat().resolvedOptions().timeZone
navigator.language
screen.width + "x" + screen.height
navigator.hardwareConcurrencyEach answer is one trait. Every site you visit can ask the same questions.
New to code? Read this first
This lab uses a little Python. You only need four ideas, and the code is mostly written for you:
- A dict is a set of labelled values, like a form:
{"timezone": "Europe/London", "screen": "1920x1080"}.person["timezone"]reads the value labelled timezone. - A list is a row of things in square brackets. Our
crowdis a list of dicts, one per visitor. - A for loop,
for person in crowd:, runs the indented lines once for each visitor. - if runs its indented lines only when a test is true.
==means "is equal to" and!=means "is not equal to".
Counting things in Python looks like this. It is the pattern you will use:
count = 0
for person in crowd:
if person["screen"] == "1366x768":
count = count + 1
print(count)What you will see
Each trait on its own is shared by several people: half the crowd is in London. But each extra trait removes everyone who differs on it. By the fourth trait, only you remain. In a real crowd of millions the same thing happens, it just takes more traits.
In other languages
The same counting in JavaScript, the language the real fingerprinting scripts are written in:
function countMatching(crowd, visitor, traits) {
let count = 0;
for (const person of crowd) {
if (traits.every(t => person[t] === visitor[t])) count++;
}
return count;
}And in Go:
func countMatching(crowd []map[string]string, visitor map[string]string, traits []string) int {
count := 0
for _, person := range crowd {
same := true
for _, t := range traits {
if person[t] != visitor[t] {
same = false
}
}
if same {
count++
}
}
return count
}Recap
- Canvas, fonts, screen, time zone, language, GPU and audio can all be read by a page script.
- Each trait alone is shared by many people; counting shows how many.
- Requiring a match on every trait shrinks the crowd fast, often down to one.
- Everything here is synthetic and stays in the page.
Fill in two functions. count_same(crowd, trait, value) returns how many visitors in crowd have that value for that trait. count_matching(crowd, visitor, traits) returns how many visitors match visitor on every trait in the list traits (if the list is empty, everyone matches). The printing at the bottom is already written: press Run and watch the crowd shrink to 1.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.