Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Unique Identifier Details

Discussion in 'Android' started by jonas-minnberg, Sep 7, 2015.

  1. jonas-minnberg

    jonas-minnberg

    Unity Technologies

    Joined:
    Oct 8, 2014
    Posts:
    6
    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)

    Code (pseudo):
    1.  
    2. string id;
    3. // Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
    4. if(checkPermission(READ_PHONE_STATE))
    5.     id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
    6. else
    7.     id = context.getContentResolver().getString(Secure.ANDROID_ID);
    8. if(!id)
    9.     id = getMacAddress();
    10. id = md5hash(id);
    11.  

    The original broken algorithm (4.6-)

    Code (pseudo):
    1.  
    2.  string id;
    3.  // Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
    4.  if(!checkPermission(READ_PHONE_STATE))
    5.     id = NULL;
    6.  else {
    7.     id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
    8.     if(id == NULL)
    9.         id = context.getContentResolver().getString(Secure.ANDROID_ID);
    10.  }
    11.  if(!id)
    12.     id = getMacAddress();
    13.  id = md5hash(id)
    14.  

    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.

    Code (CSharp):
    1.  
    2. // Hash an input string and return the hash as
    3. // a 32 character hexadecimal string.
    4. static string getMd5Hash(string input)
    5. {
    6.     if (input == "")
    7.         return "";
    8.     MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
    9.     byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
    10.     StringBuilder sBuilder = new StringBuilder();
    11.     for (int i = 0; i < data.Length; i++)
    12.         sBuilder.Append(data[i].ToString("x2"));
    13.     return sBuilder.ToString();
    14. }
    15.  
    16. static string generateDeviceUniqueIdentifier(bool oldBehavior)
    17. {
    18.     string id = "";
    19.     AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    20.     AndroidJavaObject activity = jc.GetStatic<AndroidJavaObject>("currentActivity");
    21.     AndroidJavaClass contextClass = new AndroidJavaClass("android.content.Context");
    22.     string TELEPHONY_SERVICE = contextClass.GetStatic<string>("TELEPHONY_SERVICE");
    23.     AndroidJavaObject telephonyService = activity.Call<AndroidJavaObject>("getSystemService", TELEPHONY_SERVICE);
    24.     bool noPermission = false;
    25.     try
    26.     {
    27.         id = telephonyService.Call<string>("getDeviceId");
    28.     }
    29.     catch (Exception e) {
    30.         noPermission = true;
    31.     }
    32.     if(id == null)
    33.         id = "";
    34.     // <= 4.5 : If there was a permission problem, we would not read Android ID
    35.     // >= 4.6 : If we had permission, we would not read Android ID, even if null or "" was returned
    36.     if((noPermission && !oldBehavior) || (!noPermission && id == "" && oldBehavior))
    37.     {
    38.         AndroidJavaClass settingsSecure = new AndroidJavaClass("android.provider.Settings$Secure");
    39.         string ANDROID_ID = settingsSecure.GetStatic<string>("ANDROID_ID");
    40.         AndroidJavaObject contentResolver = activity.Call<AndroidJavaObject>("getContentResolver");
    41.         id = settingsSecure.CallStatic<string>("getString", contentResolver, ANDROID_ID);
    42.         if(id == null)
    43.             id = "";
    44.     }
    45.     if(id == "")
    46.     {
    47.         string mac = "00000000000000000000000000000000";
    48.         try
    49.         {
    50.             StreamReader reader = new StreamReader("/sys/class/net/wlan0/address");
    51.             mac = reader.ReadLine();
    52.             reader.Close();
    53.         }
    54.         catch (Exception e) {}
    55.         id = mac.Replace(":", "");
    56.     }
    57.     return getMd5Hash(id);
    58. }
    59.  
     
  2. tricket

    tricket

    Joined:
    Feb 10, 2016
    Posts:
    1
    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. :)

    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?
     
  3. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    Bump. I, too, would like to know. Unique id is useful, but sometimes not at a price of a rather scary dialog.
     
  4. djarcas

    djarcas

    Joined:
    Nov 15, 2012
    Posts:
    245
    Bump and ditto.
     
  5. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
    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??
     
  6. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
  7. Klik303

    Klik303

    Joined:
    Dec 10, 2016
    Posts:
    19
    Short question. Why hash method is use on "unique identifier"?
     
    Can-Baycay likes this.
  8. JessChis

    JessChis

    Joined:
    Aug 16, 2018
    Posts:
    2
    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.
     
  9. breban1

    breban1

    Joined:
    Jun 7, 2016
    Posts:
    194
    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 https://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID).​

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

    DKK

    Joined:
    Nov 3, 2014
    Posts:
    1
    Factory reset changes it on my Samsung tablet. So you need to be prepared for that.
     
  11. breban1

    breban1

    Joined:
    Jun 7, 2016
    Posts:
    194
    @DKK thanks for the info! Agreed, I had to find a different solution (without uniquely identifying each device).
     
  12. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
    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?
     
    Last edited: Jul 31, 2019
  13. davidro_unity

    davidro_unity

    Unity Technologies

    Joined:
    Apr 18, 2019
    Posts:
    22
    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?
     
  14. flashmandv

    flashmandv

    Joined:
    Mar 26, 2015
    Posts:
    156
    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?

    Code (CSharp):
    1. FileNotFoundException: Could not find file \"/sys/class/net/wlan0/address\".
    2. System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options)
    3. System.IO.File.OpenRead (System.String path)
    4. System.IO.StreamReader..ctor (System.String path, System.Text.Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
    5. System.IO.StreamReader..ctor (System.String path)
    6. UTNotifications.PushNotifications.GenerateDeviceUniqueIdentifier ()
    Targeting Android API level 28. Unity 2018.1.1f1 and latest UTNotifications plugin 1.8.3 (from the asset store)
     
  15. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
    No this is for release builds and I'm using 2018.3.4

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

    ommzideveloper46

    Joined:
    Jul 3, 2019
    Posts:
    1
    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
     
  17. JuliusM

    JuliusM

    Unity Technologies

    Joined:
    Apr 17, 2013
    Posts:
    835
  18. JuliusM

    JuliusM

    Unity Technologies

    Joined:
    Apr 17, 2013
    Posts:
    835
    Different values compared to what? What are you changing to get different values?
     
  19. monark

    monark

    Joined:
    May 2, 2008
    Posts:
    1,598
    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.
     
  20. sathya

    sathya

    Joined:
    Jul 30, 2012
    Posts:
    297
    @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
     
    Last edited: Aug 10, 2020
  21. giggioz

    giggioz

    Joined:
    May 11, 2017
    Posts:
    52
    Same here

    What is the meaning of a SystemInfo.deviceUniqueIdentifier that changes from app to app?
    This is crazy, we used this identifier to collect info of the same device for different apps, how should we handle this?
     
  22. giggioz

    giggioz

    Joined:
    May 11, 2017
    Posts:
    52
    Ok, after a sleepless night I think I've found a solution (at least for Android)

    The problem here is that https://docs.unity3d.com/ScriptReference/SystemInfo-deviceUniqueIdentifier.html says:

    Android: SystemInfo.deviceUniqueIdentifier always returns the md5 of ANDROID_ID. (See https://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID). Note that since Android 8.0 (API level 26) ANDROID_ID depends on the app signing key. That means "unsigned" builds (which are by default signed with a debug keystore) will have a different value than signed builds (which are signed with a key provided in the player settings). Also when allowing Google Play to sign your app, this value will be different when testing locally built app which is signed with the upload key and app downloaded from the Google Play which will be signed with the "final" key.

    So, if you allow Google Play to sign your app the deviceUniqueIdentifier will change from app to app even if you sign your apps with the same key in Unity (because it will be re-signed again from Google)

    The solution is "Upgrade your app signing key for new installs" as stated here https://support.google.com/googleplay/android-developer/answer/7384423#upgrade

    You can do this only once in the lifetime of the app, so read carefully the doc

    From the link above follow this procedure

    1. Sign in to your Play Console.
    2. Select an app.
    3. At the left menu, select Release management > App signing.
    4. In the 'Upgrade your app signing key for new installs' card, select Request key upgrade.
    5. Select an option. Depending on the option that you select, you may need to contact support to complete your request.
    6. Get Google to generate a new app signing key (recommended) or upload one. After upgrading your app signing key, if you were using the same key for your app signing and upload key, you can continue using your legacy app signing key as your upload key or generate a new upload key.

    In step 6 you want to upload your own key, so you can upload the same key for all your apps, choose to do so and follow the instructions (you have to use a jar file called pepk.jar to sign your key and upload it to google.

    I did a test with one app and now the deviceUniqueIdentifier is the same for my local build and the downloaded version.

    I really hope this helps!
     
    Elmstrom likes this.