> For the complete documentation index, see [llms.txt](https://docs.talsec.app/appsec-articles/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.talsec.app/appsec-articles/glossary/device-identifiers.md).

# Device Identifiers

## Definition

**Device identifier** is any value your app can read that distinguishes one physical device from another. It covers everything from a hardware-burned serial number to a random UUID you generate yourself and stash in the Keychain.

Identifiers sit on a spectrum of scope:

* **Device-wide (hardware-scoped)** - tied to the physical device (IMEI, serial number, the Widevine DRM ID). One value, shared by every app and every user on the device.
* **App-scoped / vendor-scoped** - the same value only within your app or your publisher's family of apps (Android's `ANDROID_ID`, iOS's `identifierForVendor`).
* **Install-scoped** - dies when the app is uninstalled (Firebase Installation ID, a UUID in `UserDefaults`).

The tighter the scope, the friendlier to privacy, and the less useful it is for the cross-app tracking that regulators, Apple, and Google have spent the last decade dismantling.

## What do we use them for?

Device identifiers serve two broad camps. Both want to recognise a device, but they want different things from the identifier, and this shapes which one is a good fit.

### Growth and Analysis

Used to collect personalised data and preferences and also to serve targeted ads. This camp cares about counting and attributing activity rather than stopping a determined user, so a resettable identifier is usually enough.

Typical uses:

* Measuring unique users and installs.
* Attributing an install to the ad or campaign that drove it.
* Personalising content, preferences, and recommendations.
* Frequency capping and grouping users into A/B test cohorts.
* Serving and measuring targeted advertising.

Here the user should be able to opt out, so the platforms push toward resettable, consent-based identifiers. iOS gates the advertising identifier behind App Tracking Transparency, and Android makes the Advertising ID resettable and lets the user opt out. Persistence is not the priority, and long-lived hardware identifiers are usually the wrong tool for this camp.

### Security and Integrity

The main goal is to identify a device across sessions (app launches, installations, and even factory resets) so we can block malicious devices. The difference from the analysis camp is the adversary. The person holding the device may be actively trying to change, hide, or forge the identifier, so persistence and spoof-resistance matter far more than reach.

Typical uses:

* Blocking a device that has already abused a service.
* Preventing promo and sign-up bonus abuse and multi-accounting.
* Detecting bot farms, emulators, and automated devices.
* Rate limiting and defending against account takeover.
* Enforcing licensing and entitlements.

Because the value must survive reinstalls, resets, and deliberate tampering, this camp leans toward hardware-backed and attested signals rather than freely resettable strings. No single freely readable identifier meets every requirement, which is why security use cases often combine several signals.

## What ideal identifier contains?

Ideal identifier has following attributes:

* **Uniqueness** - any two devices from the pool of devices shouldn't have colliding value of identifier.
* **Persistence** - identifier should survive any malicious manipulation such as rebooting, reinstallation of the app, factory reset, or OS update.
* **Resettability** - the user can and should be able to wipe it intentionally. A wipeable identifier is better for privacy, but a disadvantage for security.
* **Coverage** - should be available for every device, including different OEMs, older hardware, and emulators.
* **Privacy** - the identifier shouldn't collect sensitive information just for the sake of identifying. The identifier itself shouldn't carry any meaning.
* **Resistant** - it should be impossible to spoof or forge.

## Commonly used identifiers

Each identifier below is described with its persistence behavior, its advantages, and its disadvantages. The wording of the persistence bullets is kept consistent so entries can be compared directly.

### Android

Android exposes several identifiers with very different persistence and privacy trade-offs. The options range from freely readable strings to hardware-backed attestation.

{% hint style="info" %}
**Quick rundown analysis through Android identifiers**\
\
[Fraud-Proofing an Android App: Choosing the Best Device ID for Promo Abuse Prevention](https://docs.talsec.app/appsec-articles/articles/fraud-proofing-an-android-app-choosing-the-best-device-id-for-promo-abuse-prevention)
{% endhint %}

#### [Android ID](https://developer.android.com/reference/kotlin/android/provider/Settings.Secure#android_id)

Android ID, also called SSAID, is the default no-permission identifier on Android. It is a 64-bit hex value read via `Settings.Secure.ANDROID_ID`.&#x20;

Since Android 8.0 the value is derived from the combination of the app signing key, the user profile, and the device. The same physical device therefore returns a different value to apps signed by different keys, and a different value in each user profile. Apps signed with the same key share the value.

<i class="fa-clock">:clock:</i> **Persistance**

* Survives reboots and OS updates.
* Survives app reinstall while the signing key stays the same.
* Reset by factory reset.
* Changes if the signing key changes.
* Not directly resettable by the user.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* No permissions required.
* Stable for the common case.
* Isolated per app, which is privacy-reasonable.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Reset by factory reset, so a wiped device looks new.
* Scoped per user profile, which complicates multi-user and work-profile devices.
* Plain string, so it can be spoofed on a rooted or hooked device.

{% hint style="info" %}
The value is anchored to your signing key. That is reliable when you own the app, but you cannot match it against apps you do not sign.
{% endhint %}

#### [Advertising ID](https://support.google.com/googleplay/android-developer/answer/6048248?hl=en)

The Advertising ID, also called GAID or AAID, is a resettable UUID from Google Play services meant only for ads and profiling. It is device-wide and shared across all apps, which is what makes it suitable for cross-app ad attribution and unsuitable for anything the user is meant to be able to escape. It is read through the Google Play services Advertising ID API.

<i class="fa-clock">:clock:</i> **Persistance**

* Survives reboots, OS updates, and app reinstall.
* Gets a new value on factory reset.
* The user can reset it at any time from settings.
* Returns a zeroed value when the user opts out of ad personalisation.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* Cross-app by design.
* Available on all Play-certified devices.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Unstable, because the user can reset it at any time.
* Returns no usable value when the user opts out.
* Requires the `AD_ID` permission on Android 13 and later.
* Google policy forbids linking it to other PII or persistent identifiers.
* Unavailable on devices without Google Play services.

#### [MediaDRM / Widevine device ID](https://developer.android.com/reference/android/media/MediaDrm#PROPERTY_DEVICE_UNIQUE_ID)

The interesting one for the security camp. It is a hardware-backed value exposed by the DRM subsystem, read via `MediaDrm.getPropertyByteArray(PROPERTY_DEVICE_UNIQUE_ID)` against the Widevine UUID. It is provisioned when the device sets up DRM, so it is the same for every app and every user on the device and does not depend on app or OS state. This makes it the most persistent value an app can read without special permissions.

<i class="fa-clock">:clock:</i> **Persistance**

* Survives reboots, OS updates, and app reinstall.
* Usually survives factory reset, because it is rooted in DRM provisioning rather than app or OS state.
* Not resettable by the user.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* No permissions required.
* Device-wide, so it is shared across apps and user profiles.
* Strongest persistence of any freely readable Android identifier.
* Harder to spoof than a settings string.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Missing on devices without hardware Widevine, such as many emulators, some AOSP builds like GrapheneOS, and some low-end hardware.
* Collisions are possible, because devices of the same manufacturer or model can share a value.
* Can still be spoofed on rooted devices using LSPosed or kernel-level modules that randomize it per profile.

{% hint style="warning" %}
MediaDRM is not unique enough on its own.&#x20;

Identifiers built on it usually combine the **MediaDRM ID with the device model**. The device model absorbs collision risk and the DRM value provides persistence.
{% endhint %}

#### [App Set ID](https://developer.android.com/identity/app-set-id)

Identifies a group of apps that belong to the same developer account and is read through the Google Play services App Set ID API without a prompt. Unlike the Advertising ID, the user cannot reset it manually, but it is not permanent either.

<i class="fa-clock">:clock:</i> **Persistance**

* Survives reboots, OS updates, and app reinstall while at least one app in the set remains installed.
* Reset by factory reset.
* Reset when all apps in the set are uninstalled.
* Reset after about 13 months without use.
* Not resettable by the user.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* No permissions required.
* Built for analytics and fraud prevention, according to Google.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Cannot be used for ad personalisation or linked to the Advertising ID.
* Plain string, so it can be spoofed on a rooted or hooked device.
* Available only on Android devices with Google Play services.

#### [IMEI / MEID / Serial number](https://source.android.com/docs/core/permissions/immutable-device-ids)

These are identifiers stamped onto the physical device at the factory. The IMEI (usually 15 digits, viewable by dialing `*#06#`) identifies the phone's cellular hardware to the mobile network, and is separate from the SIM, which identifies the subscriber. The MEID is the older equivalent from CDMA networks and is mostly legacy today. The serial number is the manufacturer's label for the unit and has nothing to do with cellular, so a Wi-Fi-only device has one but no IMEI. All three are globally unique and burned into the hardware, so they survive reinstalls and factory resets. That permanence is exactly why access to them is now heavily restricted.

<i class="fa-clock">:clock:</i> **Persistance**

* Permanent, because the value is burned into the hardware or modem.
* Survives factory reset.
* Not resettable by the user.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* Device-wide and non-resettable

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Restricted to privileged apps since Android 10, so normal apps cannot read them.
* The value is meaningful and counts as sensitive data, which conflicts with the privacy attribute.
* IMEI and MEID exist only on devices with a cellular modem.

{% hint style="danger" %}
Historically used, but practically dead for normal app use today.
{% endhint %}

#### [MAC Address](https://source.android.com/docs/core/permissions/immutable-device-ids)

The MAC address is the hardware address of the device network interface. It was once a convenient device-wide identifier, but both the platform and the hardware vendors now hide the real value.

<i class="fa-clock">:clock:</i> **Persistance**

* The real hardware address is permanent, but apps can no longer read it.
* Since Android 10 the device uses a randomised MAC per network by default.
* The randomised value changes per network, so it is not stable.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* None that are usable in practice today.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Apps read a randomised or placeholder value, not the real address.
* The factory address is hidden behind a privileged permission.
* Randomisation changes the value per network, so it cannot be used for identification.

{% hint style="danger" %}
Like IMEI, the MAC address is effectively unavailable for device identification today.
{% endhint %}

#### [Hardware-backed Keystore](https://developer.android.com/privacy-and-security/keystore) + [Key Attestation](https://source.android.com/docs/security/features/keystore/attestation)

This is not a value you read, but a key you create. You generate a key pair inside the Android Keystore, backed by the TEE (Trusted Execution Environment) or by StrongBox, a separate tamper-resistant chip. The private key cannot leave secure hardware. Key Attestation then produces a certificate chain that your server can verify to confirm the key was generated in real hardware. Instead of trusting a value the client reports, your server verifies a signature the client cannot forge.

<i class="fa-clock">:clock:</i> **Persistance**

* The key survives reboots and OS updates.
* The key is tied to the app and is lost on uninstall unless you design around it.
* Not a device-wide value, so it does not identify a device across apps on its own.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* The private key cannot be copied off the device.
* Your server verifies it cryptographically, so it does not rely on a reported string.
* Strongly resistant to spoofing.
* No permissions required.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* More work to implement than reading a value.
* Attestation coverage and quality vary between manufacturers.
* StrongBox is not available on every device.
* The key is lost on uninstall, so it is not a persistent device identifier on its own.

{% hint style="warning" %}
**This is an integrity mechanism, not a stored identifier.**&#x20;

Use it to prove a request comes from genuine hardware, not to recognise a device across installs.
{% endhint %}

#### [Play Integrity API](https://developer.android.com/google/play/integrity/overview)

Play Integrity is not an identifier. It is a signal from Google that tells your server whether the app is genuine and unmodified, whether the device is genuine, and whether the app came from an official source. You request a token on the device and verify it on your server. It answers whether you can trust the client, not which device it is.

<i class="fa-clock">:clock:</i> **Persistance**

* Not applicable, because it returns a per-request verdict rather than a stored value.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* Strong, server-verified signal of app and device genuineness.
* Hardware-backed on modern devices, so it is hard to spoof.
* Designed for abuse and fraud protection.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Cannot be used as an identifier, because there is no stable value to store.
* Depends on Google Play services and is subject to rate limits and outages.
* Can produce failures on custom setups such as GrapheneOS or rooted devices, which can lock out genuine users.
* Google recommends against using it as the only allow or deny decision.

{% hint style="info" %}
Treat this as one signal among several. It tells you whether to trust a device, not how to identify one, and it should not be your only gate.
{% endhint %}

#### Google Services Framework ID (GSF ID)

The GSF ID is a device-wide value created when the device registers with the Google Services Framework, the component behind Google Play services. It is read by querying an internal content provider (`content://com.google.android.gsf.gservices`, key `android_id`) and returns the same value to every app on the device. It is not an official or documented identifier API, and Google does not recommend relying on it.

**Persistence**

* Survives reboots, OS updates, and app reinstall.
* Reset by factory reset.
* Reset when Google Services Framework data is cleared or the device re-registers with Google.
* Not resettable by the user through a normal setting.

**Advantages**

* Device-wide, so it is shared across apps and user profiles.
* No dedicated permission for the query in practice.

**Disadvantages**

* Not an official or documented API, so it can change or break without notice.
* Available only on devices with Google services, so it is missing on non-GMS devices.
* Tied to the Google registration and shared across apps, which is a privacy concern.
* Plain value read from a provider, so it can be spoofed on a rooted or hooked device.

{% hint style="warning" %}
This is an unofficial identifier read from an internal provider. Prefer the App Set ID or a value you generate yourself.
{% endhint %}

#### [Firebase Installation ID (FID)](https://firebase.google.com/docs/projects/manage-installations)

The Firebase Installation ID identifies one installation of your app on one device. It is provided by the Firebase Installations SDK, read via `FirebaseInstallations.getInstance().getId()`, and is used internally by Firebase services such as Cloud Messaging, Analytics, and Remote Config. It is available on both Android and iOS when you use Firebase.

**Persistence**

* Survives reboots and OS updates.
* Lost on app uninstall, because it is tied to the installation.
* A new value is created on reinstall.
* Can be deleted and regenerated through the API.

**Advantages**

* Simple to use if you already use Firebase.
* Cross-platform, available on Android and iOS.
* Per-install scope, which is privacy-reasonable.

**Disadvantages**

* Identifies an installation, not a device, so a reinstall looks like a new identity.
* Not suitable for device blocklisting.
* Requires the Firebase SDK.

{% hint style="info" %}
Use this to identify an app installation for Firebase features, not to recognise a device across reinstalls.
{% endhint %}

#### [Instance ID (IID)](https://developers.google.com/instance-id)

Instance ID was the older Firebase and Google Cloud Messaging identifier that provided a unique ID per app instance together with messaging tokens. It has been deprecated and split into two replacements: the Firebase Installation ID for the identifier, and Cloud Messaging registration tokens for messaging. It is listed here mainly so you recognise it in older code.

**Persistence**

* Behaved like the Firebase Installation ID: tied to the app installation and lost on uninstall.

**Advantages**

* None today, because it is deprecated.

**Disadvantages**

* Deprecated and replaced by Firebase Installations and Cloud Messaging tokens.
* Should not be used in new code.

{% hint style="danger" %}
Deprecated. Use the Firebase Installation ID and Cloud Messaging tokens instead.
{% endhint %}

### iOS

Apple does not provide a stable device-wide identifier to third-party apps by design. The options below are either scoped to your own apps, resettable, or integrity mechanisms rather than identifiers.

#### [identifierForVendor (IDFV)](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor)

The identifier for vendor, or IDFV, is a UUID that iOS assigns to all apps from the same vendor on a device. Vendor is determined by the first parts of the bundle identifier, for example `com.example`. It is read via `UIDevice.current.identifierForVendor`. It is the standard no-permission identifier for first-party analytics across your own apps, and it is the closest iOS has to Android ID.

<i class="fa-clock">:clock:</i> **Persistance**

* Survives reboots and OS updates.
* Stays the same while at least one app from the vendor is installed.
* Reset when the user removes all of the vendor apps, so a single app gets a new value after reinstall.
* Reset by factory reset.
* Not directly resettable by the user.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* No permission and no tracking prompt required.
* Available immediately when the app launches.
* Shared across all of your own apps on the device.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Resets when the user removes all of your apps.
* Scoped to your vendor account, so it cannot match users across other publishers.
* Plain value, so it can be spoofed on a jailbroken device.

#### [UDID and MAC address](https://developer.apple.com/documentation/uikit/uidevice)

The UDID was the original device-wide identifier on iOS, and the MAC address was a common substitute after it was removed. Both are now closed to third-party apps and are listed here mainly so you recognise them in older code.

<i class="fa-clock">:clock:</i> **Persistance**

* Both were permanent when they were available, but neither is accessible to apps today.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* None today

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* The UDID has not been available to third-party apps for many years.
* The MAC address returns a fixed placeholder value since iOS 7.
* Neither can be used for device identification.

{% hint style="danger" %}
Do not design around these. They are no longer available to apps.
{% endhint %}

#### [Keychain-stored UUID](https://developer.apple.com/documentation/security/keychain-services)

When the platform identifiers do not fit, a common pattern is to generate your own UUID and store it in the Keychain. The Keychain is not cleared when the app is deleted, unlike `UserDefaults`, so the value can survive reinstalls. This gives you an identifier you fully control.

<i class="fa-clock">:clock:</i> **Persistance**

* Survives reboots, OS updates, and app reinstall in practice, because the Keychain is not cleared on delete.
* Reset by factory reset.
* Not resettable by the user unless you provide a way.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* Fully under your control.
* Survives app reinstall in practice.
* No permissions required.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Survival across reinstall is not guaranteed by Apple.
* Scoped to your app, not the whole device.
* Client-side value, so it can be spoofed on a jailbroken device.

{% hint style="warning" %}
Apple has stated that Keychain persistence after app deletion was never a documented contract, only an implementation detail that could change.&#x20;

Build on it, but design for the day it stops behaving.
{% endhint %}

#### [DeviceCheck](https://developer.apple.com/documentation/devicecheck)

**DeviceCheck is not an identifier.** It gives you two bits of storage per device that Apple keeps on its own servers, and those bits survive app deletion and reinstall. A common use is to record whether a device has already used a one-time offer, such as a free trial. Your server reads and writes the bits through Apple, and the token the device produces via `DCDevice` is different on every call.

<i class="fa-clock">:clock:</i> **Persistance**

* The two bits survive reboots, app reinstall, and app deletion, because Apple stores them server-side.
* The token itself is not persistent, because it changes on every call.
* Not resettable by the user.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* Server-verified state that survives reinstall.
* Cannot be cleared by reinstalling the app.
* No permissions required.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Only two bits of state, not an identifier.
* Cannot uniquely identify a device.
* Requires your server to call Apple to read or write the bits.

{% hint style="info" %}
Use this to remember a small fact about a device, not to identify it.
{% endhint %}

#### [App Attest](https://developer.apple.com/documentation/devicecheck/dcappattestservice)

App Attest is the iOS equivalent of Android Key Attestation. It uses a hardware-backed key in the Secure Enclave together with attestation to prove that a genuine copy of your app is running on a genuine Apple device. Like Play Integrity, it answers whether you can trust the client rather than which device it is.

<i class="fa-clock">:clock:</i> **Persistance**

* The attestation key lives in the Secure Enclave and is tied to the app.
* Not a stable device identifier, because it is an integrity mechanism.

<i class="fa-circle-check">:circle-check:</i> **Advantages**

* Hardware-backed and verified on your server.
* Strongly resistant to spoofing.
* The right tool when the question is whether to trust the client.

<i class="fa-circle-exclamation">:circle-exclamation:</i> **Disadvantages**

* Not an identifier, so there is no stable value to store.
* More work to implement, and it needs a network call to attest.
* Available on iOS 14 and later.

{% hint style="info" %}
Like Key Attestation on Android, this proves trust in a client. It does not identify a device across installs.
{% endhint %}

## Further reading

* [Mobile API Anti-abuse Protection with AppiCrypt: A New Play Integrity and DeviceCheck Alternative](https://docs.talsec.app/appsec-articles/articles/mobile-api-anti-abuse-protection-with-appicrypt-r-a-new-play-integrity-and-devicecheck-alternative)
* [EUDI App Attestation Choices: AppiCrypt vs. Google Play Integrity and Apple App Attest](https://docs.talsec.app/pages/GlYpJvaTbzqZl970laFM)
* [iOS Keychain vs. Android Keystore](https://docs.talsec.app/pages/pSdjbBonypf3bDiRkXg3)
* [Introducing Multi-Instancing Detection for freeRASP](https://docs.talsec.app/pages/7WLpOeny9JeBPLGr1AlG)
* [AppiCrypt Against Time Spoofing: From Free Trial Abuse to License Fraud](https://docs.talsec.app/pages/Vo2Uzmm4yMN2chEByCGs)
* [Emulators in Gaming: Threats and Detections](https://docs.talsec.app/appsec-articles/articles/emulators-in-gaming-threats-and-detections)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.talsec.app/appsec-articles/glossary/device-identifiers.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
