Device fingerprint
Also called: device fingerprinting, hardware fingerprint
Last updated
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 some systems also compare them with each other for consistency.
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 (Android 10 privacy changes). Since Android 6, WifiInfo.getMacAddress() and BluetoothAdapter.getAddress() return the placeholder 02:00:00:00:00:00 to every third-party caller (Android 6.0 changes).
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 more: beyond the common entries — accelerometer, gyroscope, magnetometer, proximity, ambient light — many handsets add vendor composite sensors named like "Samsung Pickup Gesture", and the exact names, vendors and resolutions vary with the firmware. 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.
Consistency signals a platform may weigh
Detection rarely hunts for a single rare value. More often it compares two values and asks whether they are easy to reconcile. Each comparison is a signal that goes into a score, weighted by each platform in its own way, rather than a pass-or-fail test. Typical pairs:
- Timezone versus exit IP — a device set to America/New_York behind a proxy exiting in Sao Paulo. A single geolocation lookup surfaces it, and it is entirely set by the operator.
- 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 an ARM chipset, the signature of an x86 emulator.
- Software renderer — GL_RENDERER naming a software rasteriser such as "SwiftShader" or "llvmpipe", which desktop emulators fall back to and which no retail handset uses to draw its screen.
- Security patch versus build — ro.build.version.security_patch dated in the future, or later than the date the build itself was made.
- Sensors versus model — some SDKs read the sensor list and weigh it against the model the device names. How much it counts, and against what reference, is not something they publish.
Reading a fingerprint yourself
Most of these values are readable over ADB without root. The commands show what the system reports; they do not tell you how a given app scores the answers, because platforms do not publish their scoring.
- adb shell getprop ro.build.fingerprint — the build string apps read, in the brand/product/device:version format described under build.prop.
- adb shell settings get secure android_id — must differ across every device in a fleet.
- adb shell dumpsys sensorservice — the sensors the device declares, with name and vendor for each.
- adb shell dumpsys battery — level, status and temperature as the system reports them.
- adb shell cat /proc/cpuinfo — an Intel or AMD model name points to x86 hardware underneath, which is how desktop emulators run.
- adb shell pm list packages -s — the preloaded system packages, which vary by brand and by region.
Consistency matters more than uniqueness
The common mistake is chasing a unique fingerprint and ignoring whether it holds together. A phone whose timezone contradicts the country its IP belongs to is not more anonymous for being unique; it simply gives a scoring system a reason to look closer. Comparisons between two values on the same device are cheap to run, because they need no reference population. That is why region coherence — timezone, locale and language agreeing with the exit IP — is the first thing to get right, and it is entirely in the operator’s hands.
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. What an account needs is a device whose values stay the same from one session to the next, and differ from every other device in the fleet.
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. Cloud phone vs antidetect browser shows which accounts need which.
The layer no property edit reaches
Google’s Play Integrity API does not score properties one by one. It relies on attestation from the device’s keystore and returns labels such as MEETS_BASIC_INTEGRITY, MEETS_DEVICE_INTEGRITY and MEETS_STRONG_INTEGRITY. According to Google’s verdict documentation, MEETS_DEVICE_INTEGRITY means a genuine, certified Android device, and on Android 13 and later it requires hardware-backed proof of a locked bootloader and a certified manufacturer image. A rooted handset with a rewritten build.prop can present a flawless property set and still miss that label, because the attestation does not come from the property area.