Device fingerprint
Also called: device fingerprinting, hardware fingerprint
A device fingerprint is the combination of hardware and software values an app reads to recognise a specific device — build properties, screen metrics, sensor list, codec list, GPU strings, timezone, locale and installed packages. Individually common, together they are near-unique, and they are checked against each other for contradictions.
No single value identifies a phone. A device fingerprint is what you get when you read dozens of them at once and treat the combination as an identity. A screen resolution of 1080x2400 is shared by hundreds of millions of handsets; a timezone of Europe/Paris by tens of millions. Put twenty such values together and the set becomes specific enough to recognise the same device weeks later, with no cookie and no login.
The values an app actually reads
Almost all of it comes from a handful of Android APIs that need no permission at all. That is the point: fingerprinting is attractive precisely because it is silent and unprompted.
- Build class — Build.MANUFACTURER, Build.BRAND, Build.MODEL, Build.DEVICE, Build.BOARD, Build.HARDWARE and Build.FINGERPRINT, all sourced from build.prop.
- Settings.Secure.ANDROID_ID — the per-app Android ID, stable across reinstalls.
- DisplayMetrics — widthPixels, heightPixels, densityDpi, plus refresh rate and cutout geometry.
- SensorManager.getSensorList(TYPE_ALL) — vendor string, name and resolution of every sensor the device claims.
- MediaCodecList — the full list of hardware and software codecs, which differs by chipset and by firmware version.
- GLES20.glGetString(GL_RENDERER) and GL_VENDOR — the GPU as the driver names it, e.g. "Adreno (TM) 730" or "Mali-G710".
- Build.SUPPORTED_ABIS, /proc/cpuinfo, TimeZone.getDefault(), Locale.getDefault() and PackageManager.getInstalledPackages().
What Android has taken away
Three of the classic identifiers are no longer readable by an ordinary app, and any article that still treats them as app-side signals is out of date. Since Android 10, TelephonyManager.getImei() requires READ_PRIVILEGED_PHONE_STATE, a permission only system apps and carrier apps can hold, and Build.getSerial() is restricted the same way. Since Android 6, WifiInfo.getMacAddress() and BluetoothAdapter.getAddress() return the placeholder 02:00:00:00:00:00 to every third-party caller.
They still matter, at a different layer: the IMEI is visible to the carrier, the MAC address to everything on the local network, and both to whoever provisions the device. What changed is that the app-side fingerprint now leans far harder on sensor lists, codec lists and GPU strings.
Where the entropy actually lives
Some values carry far more information than others. Screen resolution contributes perhaps four or five bits across the whole Android population. The sensor list contributes far more: a current retail handset enumerates roughly 25 to 40 sensors — accelerometer, gyroscope, magnetometer, proximity, ambient light, barometer, step counter, plus a long tail of vendor composite sensors named like "Samsung Pickup Gesture". Those entries are firmware-specific and hard to guess. The installed-package list is similar: the exact set of preloaded carrier and OEM packages narrows a device to a model, a region and often a firmware branch.
The contradictions that appear most often
Detection rarely hunts for a rare value. It looks for two values that cannot both be true. These are the pairs that fail most frequently in practice:
- Timezone versus exit IP — a device set to America/New_York behind a proxy exiting in Sao Paulo, one geolocation lookup from being flagged.
- Locale versus SIM — Locale.getDefault() returning en_US while the SIM reports a Brazilian MCC of 724.
- ABI versus chipset — Build.SUPPORTED_ABIS listing x86_64 first on a device whose build.prop claims a Snapdragon.
- GPU versus brand — GL_RENDERER returning "SwiftShader" on a device claiming a Galaxy S23.
- Security patch versus build — ro.build.version.security_patch dated after the build, or a date Google never published for that model.
- Battery versus physics — 100 percent and exactly 25.0 °C on every read, indefinitely.
- Sensor count versus form factor — a claimed flagship enumerating six sensors, none of them a barometer or step counter.
Reading a fingerprint yourself
Everything above is inspectable over ADB in a few minutes. Run these against any device you are evaluating, including one a provider has just handed you:
- adb shell getprop ro.build.fingerprint — the single string that should name a real, shipped firmware.
- adb shell settings get secure android_id — must differ across every device in a fleet.
- adb shell dumpsys sensorservice — count the entries; under ten on a claimed flagship is the tell.
- adb shell dumpsys battery — watch level and temperature over an hour; constants mean synthesis.
- adb shell cat /proc/cpuinfo — an Intel or AMD model string on an ARM-claiming device ends the discussion.
- adb shell pm list packages -s — the preloaded set should match the brand and region claimed.
Consistency matters more than uniqueness
The common mistake is chasing a unique fingerprint and ignoring whether it holds together. A device claiming a Samsung handset while reporting sensors no Samsung ships, or a phone whose timezone contradicts the country its IP belongs to, is not anonymous — it is conspicuous. Internal contradictions are far easier to detect than repetition, because they need no reference population: one device, two values, done.
The second requirement is stability. A fingerprint that changes between sessions reads as a different device each visit, so randomising values per boot is the opposite of helpful for a long-lived account. Real hardware gives you both properties for free, because the values come from silicon rather than from a generator.
Fingerprint versus browser fingerprint
Browser fingerprinting reads canvas rendering, WebGL parameters, installed fonts, audio-context output and user agent from inside a web page. Device fingerprinting happens at the operating-system level and reaches the sensor list, the codec list and the package list, none of which a web page can see. An antidetect browser addresses the first surface, a genuinely separate device the second — which is why the two are used together rather than as alternatives.
The layer no property edit reaches
Google’s Play Integrity API reads no properties at all. It asks the device’s hardware-backed keystore to sign a statement, returning MEETS_BASIC_INTEGRITY, MEETS_DEVICE_INTEGRITY or MEETS_STRONG_INTEGRITY. The middle verdict requires a Google-certified device with an unmodified bootloader. An emulator or a rooted image with a rewritten build.prop can present a flawless property set and still fail, because the signature comes from a key the spoofing never touches.
