Unique Identifier Details

Android Unique Identifier

The Unique Identifier on Android has gone through a few changes due to bugs, so there is a chance you will end up in a situation where you are relying on an ID that has changed between Unity versions.

This page tries to document the behavior so you can work around this.

Description of the Algorithm

The original plan was to use getDeviceId() from TELEPHONY_SERVICE if permitted and available, and fall back to ANDROID_ID if that failed and MAC address as a last resort.

But in older versions of Unity (4.5 and back) there was a bug; if the application did not have the READ_PHONE_STATE permission, the ANDROID_ID path was not used either, so it always fell back to the MAC address.

This was later “fixed” by checking for the READ_PHONE_STATE permission. But now a new problem was introduced; ANDROID_ID would not be used if we had the permission but getDeviceId() still returned NULL.

Rather then making another fix and creating a third way to calculate the identifier, the old behavior was later reintroduced.

The “fixed” behavior still ended up in 5.0+ so that is why it is the current algorithm.

Another problem is that some Android tablets fail to return MAC address when WiFi is turned off, possibly resulting in a different ID.

The current algorithm (5.0+ and possibly some version of 4.6)

string id;
// Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
if(checkPermission(READ_PHONE_STATE))
    id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
else
    id = context.getContentResolver().getString(Secure.ANDROID_ID);
if(!id)
    id = getMacAddress();
id = md5hash(id);

The original broken algorithm (4.6-)

 string id;
 // Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
 if(!checkPermission(READ_PHONE_STATE))
    id = NULL;
 else {
    id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
    if(id == NULL)
        id = context.getContentResolver().getString(Secure.ANDROID_ID);
 }
 if(!id)
    id = getMacAddress();
 id = md5hash(id)

Reading the MAC address

The current implementation iterates over all interfaces, and gets the hardware address of the first interface that is not the loopback interface.
Result is then transformed to a 12 chars long lower case hex string.
If the mac could not be read it is set to “00000000000000000000000000000000” (32 zeroes).

Simulating the ID in C#

The following script is an example on how to generate the ID yourself, to emulate older or newer behavior. It is not fully tested and the way it reads the MAC address is not the same as the native code, but hopefully it is a place to start if you need this functionality.

// Hash an input string and return the hash as
// a 32 character hexadecimal string.
static string getMd5Hash(string input)
{
    if (input == "")
        return "";
    MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
    byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
    StringBuilder sBuilder = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
        sBuilder.Append(data[i].ToString("x2"));
    return sBuilder.ToString();
}

static string generateDeviceUniqueIdentifier(bool oldBehavior)
{
    string id = "";
    AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    AndroidJavaObject activity = jc.GetStatic<AndroidJavaObject>("currentActivity");
    AndroidJavaClass contextClass = new AndroidJavaClass("android.content.Context");
    string TELEPHONY_SERVICE = contextClass.GetStatic<string>("TELEPHONY_SERVICE");
    AndroidJavaObject telephonyService = activity.Call<AndroidJavaObject>("getSystemService", TELEPHONY_SERVICE);
    bool noPermission = false;
    try
    {
        id = telephonyService.Call<string>("getDeviceId");
    }
    catch (Exception e) {
        noPermission = true;
    }
    if(id == null)
        id = "";
    // <= 4.5 : If there was a permission problem, we would not read Android ID
    // >= 4.6 : If we had permission, we would not read Android ID, even if null or "" was returned
    if((noPermission && !oldBehavior) || (!noPermission && id == "" && oldBehavior))
    {
        AndroidJavaClass settingsSecure = new AndroidJavaClass("android.provider.Settings$Secure");
        string ANDROID_ID = settingsSecure.GetStatic<string>("ANDROID_ID");
        AndroidJavaObject contentResolver = activity.Call<AndroidJavaObject>("getContentResolver");
        id = settingsSecure.CallStatic<string>("getString", contentResolver, ANDROID_ID);
        if(id == null)
            id = "";
    }
    if(id == "")
    {
        string mac = "00000000000000000000000000000000";
        try
        {
            StreamReader reader = new StreamReader("/sys/class/net/wlan0/address");
            mac = reader.ReadLine();
            reader.Close();
        }
        catch (Exception e) {}
        id = mac.Replace(":", "");
    }
    return getMd5Hash(id);
}
8 Likes

Thanks, jonas.minnberg – a further question: in the various posts, I keep seeing that the reported identified will change between 5.3 and 5.4. I can’t see from your pseudo-code examples why that is – won’t we receive the hash of either (a) the deviceId() value if available, or (b) the ANDROID_ID?

We’re trying to determine how much storing of old value => new value we need to do in our code, so if the result isn’t going to change from 5.3 to 5.4, that’ll save us some headaches. :slight_smile:

Also: for Android 6.0, we want to explicitly TURN OFF the requested READ_PHONE_STATE permission (as it’s creating a scary “Allow app to make and manage phone calls?” … does requesting deviceUniqueIdentifier automatically add this, and do we need to strip out this permission via a post-build script, in order to make sure we’re not asking for that permission?

6 Likes

Bump. I, too, would like to know. Unique id is useful, but sometimes not at a price of a rather scary dialog.

Bump and ditto.

Getting a bizarre situation with this using U5.6.3
I have 11 identically coded projects that just use different resources.
All 11 have been published before and did not exhibit any unexpected permissions issues with U5.3.8
Now 9 of them are fine and 2 request permissions for the phone.
All have been updated to the same plugin version - I only use 1 from Prime31
Before building the manifest files are all identical, just the product name changes
I do use SystemInfo.deviceUniqueIdentifier, which apparently is fixed, so why would 2 of them have an issue with that now suddenly??

Ok I found the issue, those 2 projects had another plugin folder in them that I wasn’t using, removing that extra plugin fixed the issue.
I think it might have been related to the discussion in this thread:
Unity 5.1 adds android permission READ_PHONE_STATE automatically, how to remove it? page-3#post-2643699

Where a target sdk is required, not sure though, as removing them works for me.

Short question. Why hash method is use on “unique identifier”?

1 Like

To mask potentially personal information. The lawyers prefer we hash things like this, just in case. For the purposes of an ID, it makes no difference. For the purposes of hacking someone, the hash is useless.

I’m considering using system.deviceUniqueIdentifier in Unity 2017 LTS to uniquely identify players. Is it safe to use it for that purpose? I would like to use it for Android AND iOS. I’m finding all kinds of answers to this with searches on the internet, but from the most recent Unity docs it seems completely safe (I’m fine with the pre-iOS7 device caveat). Hoping a Unity person can reply so I know for sure.

According to the Unity docs:
iOS: on pre-iOS7 devices it will return hash of MAC address. On iOS7 devices it will be UIDevice identifierForVendor or, if that fails for any reason, ASIdentifierManager advertisingIdentifier.

Android: SystemInfo.deviceUniqueIdentifier always returns the md5 of ANDROID_ID. (See Settings.Secure  |  Android Developers).

Does it guarantee that it also won’t change (OS updates, factory reset, etc.)?

Factory reset changes it on my Samsung tablet. So you need to be prepared for that.

@DKK thanks for the info! Agreed, I had to find a different solution (without uniquely identifying each device).

This has recently started returning different values for different apps on the same phone, but not on all phones, it’s ok on my Huawei but not my Samsung S9, has something fundamental changed there?

Is the difference that you’re seeing only for Development builds? And did it start with Unity 2019.1?

Also, what Android version is on those phones?

Hi all.
Our game uses UTNotifications Unity plugin, which tries to read new StreamReader(“/sys/class/net/wlan0/address”) and it fails massively on multiple android phones.
Do you have any solution to this exception?

FileNotFoundException: Could not find file \"/sys/class/net/wlan0/address\".
System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options)
System.IO.File.OpenRead (System.String path)
System.IO.StreamReader..ctor (System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
System.IO.StreamReader..ctor (System.String path)
UTNotifications.PushNotifications.GenerateDeviceUniqueIdentifier ()

Targeting Android API level 28. Unity 2018.1.1f1 and latest UTNotifications plugin 1.8.3 (from the asset store)

No this is for release builds and I’m using 2018.3.4

Android versions:
Samsung S9: v9
Huawei: v8.1.0

I am using Unity 2019.2.11 and am getting different values for “SystemInfo.deviceUniqueIdentifier” on Android Device: Redmi Note 7 Pro. Android Version: 9

That is an expected behavior. See https://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID “unique to each combination of app-signing key, user, and device”.

Different values compared to what? What are you changing to get different values?

So each app I make generates a new unique device id.
Whereas it used to generate the same id as you’d expect being a device id not an app id.

We have a bunch of apps that all work together and having a way to identify they were all running on the same device was important.

@JuliusM
Same issue with ios every time I do a fresh install i get different deviceinfo
calling SystemInfo.deviceUniqueIdentifier gives following results
on first time install: 2BA21064-6018-4820-A7DB-34E68BB3C2B0
on second time install: 8FFF4A85-4C0D-472F-8D07-3E3A43642960
and so on