31

Our client's app main feature is heavily relaying on tracking their clients' devices, they offer products that are bound to the specific phone(not its owner). This was possible using the device imei, but with the privacy changes in Android 10, they made it unreachable. (https://developer.android.com/about/versions/10/privacy/changes).

Android has a documentation about what identifier to use on specific user cases, but non matches our case since we need it to be unique, constant and bound to the device(or at least difficult to change). https://developer.android.com/training/articles/user-data-ids. I'm considering Android ID to be a possible solution, or using the mac address knowing they aren't 100% reliable.

Any thoughts? recommendations? experiences? at this point anything could be an option

kvnnj.m_av
  • 311
  • 1
  • 3
  • 4

5 Answers5

25

I advice you to read the official blog of the best practice of google to see what the use case match with your specification : https://developer.android.com/training/articles/user-data-ids.html

For me i occcured the same problem about the unicity of android identifiers and i found the only solution is to use the MediaDrm API ( https://android.googlesource.com/platform/frameworks/base/+/android-cts-4.4_r1/media/java/android/media/MediaDrm.java#539 ) which contains a unique device id and can survive even on the factory reset and doesn't need any additional permission on your manifest file.

Here is the couple of code how can we retreive the unique identifier on Android 10 :

import android.media.MediaDrm
import java.security.MessageDigest
import java.util.*

object UniqueDeviceID {

    /**
     * UUID for the Widevine DRM scheme.
     * <p>
     * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up.
     */
    fun getUniqueId(): String? {

        val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
        var wvDrm: MediaDrm? = null
        try {
            wvDrm = MediaDrm(WIDEVINE_UUID)
            val widevineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
            val md = MessageDigest.getInstance("SHA-256")
            md.update(widevineId)
            return  md.digest().toHexString()
        } catch (e: Exception) {
            //WIDEVINE is not available
            return null
        } finally {
            if (AndroidPlatformUtils.isAndroidTargetPieAndHigher()) {
                wvDrm?.close()
            } else {
                wvDrm?.release()
            }
        }
    }


    fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
}
Sofien Rahmouni
  • 4,354
  • 1
  • 21
  • 22
  • Did you test it on multiple device ? – SRB Bans Dec 04 '19 at 12:57
  • Yes,i test it on my test devices : Samsung Note 5 ,Pixel 3 – Sofien Rahmouni Dec 04 '19 at 13:59
  • Okay.. great.. :) – SRB Bans Dec 04 '19 at 14:07
  • You can add another DRM UUID to failsafe when a exception is raised and retry. val COMMON_PSSH_UUID = UUID(0x1077EFECC0B24D02L, -0x531cc3e1ad1d04b5L) val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) – firetrap Feb 25 '20 at 22:13
  • 1
    it is changed after the factory reset. – Vasu Mar 05 '20 at 05:01
  • @firetrap did you try how to extract other UUIDs, like COMMON_PSSH_UUID – Momin Khan Nov 16 '21 at 13:42
  • @SofienRahmouni "a unique device id and can survive even on the factory reset". According to [@] Vasu 's comment "it is changed after the factory reset". Can you please both elaborate and give some links to documentation about how WIDEVINE_UUID behaves regarding factory reset? Thanks – Pascal Aug 14 '22 at 12:23
  • @Vasu "it is changed after the factory reset". Can you please elaborate and give some links to documentation about how WIDEVINE_UUID behaves regarding factory reset? Thanks – Pascal Aug 14 '22 at 12:24
19

For Java users that are interested in Sofien's solution, I have:

  1. Converted Sofien's code to Java and further simplified;
  2. Extensively tested on Android 10 (API 29), Android 11 (API 30) and previous versions.

1. Code and discussion

@Nullable
String getUniqueID() {
   UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
   try {
      MediaDrm wvDrm = new MediaDrm(wideVineUuid);
      byte[] wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
      return Arrays.toString(wideVineId);
   } catch (Exception e) {
      // Inspect exception
      return null;
   }
   // Close resources with close() or release() depending on platform API
   // Use ARM on Android P platform or higher, where MediaDrm has the close() method
}

There are two key differences w.r.t. Sofien's code.

  • I am not using the MessageDigest, which results in a simpler code. Moreover, the MessageDigest.update() method applies the SHA-256 hash function to its argument, which introduces an extremely low probability of losing UUID uniqueness. The only drawback of not hashing the UUID is that you don't have a fixed length UUID, which I don't care about in my application.
  • Instead of the Kotlin function toHexString (which has no one-line counterpart in Java) I am using Arrays.toString. This choice is safe because (A) It throws no Exception and (B) it retains a one-to-one correspondence between the wideVineId and its String representation. If you prefer to stick to hex conversion, the Apache Commons Codec library offers a one-line solution.

Of course, these changes result in a different UUID, needless to say that other choices are possible. Notice also that an UUID generated with Arrays.toString takes the form

[92, -72, 76, -100, 26, -86, 121, -57, 81, -83, -81, -26, -26, 3, -49, 97, -24, -86, 17, -106, 25, 102, 55, 37, 47, -5, 33, -78, 34, 121, -58, 109]

So, if you don't want special characters in your UUID you can remove them with String.replaceAll().

2. Tests

I have tested the persistence of the UUID

  • over reinstallation
  • over reinstallation AND reboot

on the following device/OS combinations:

  • Google Pixel 4A / API 30
  • Samsung Galaxy S10 / API 29
  • Samsung Galaxy S9 / API 29
  • Huawei Nexus 6P / API 27 (tested also factory reset)
  • LG V20 / API 27 (tested also factory reset)
  • Asus ZenFone 2 / API 23
  • Samsung Galaxy J5 / API 23
  • LG Nexus 5 / API 23
  • LG K4 / API 22
  • Samsung Galaxy J3 / API 22
  • Samsung Galaxy S4 / API 21

In all of the tests, the targetSdkVersion is 30. More tests (especially on API 29 and 30) are welcome.

MathMax
  • 571
  • 7
  • 22
  • 3
    have you also tested after factory reset? is it the same? – Iulia Barbu Mar 07 '20 at 08:28
  • 2
    @IuliaBarbu Thank you for your comment. I have now tested the factory reset on one of the listed devices (see updated answer). I could not test factory reset on all devices, since they belong to actual users. – MathMax Mar 09 '20 at 12:39
  • is there a way to get a shorter value, or how to make the array shorter? – jvargas Mar 11 '20 at 16:24
  • @jvargas You could use the hex conversion I have mentioned, instead of the array representation. That would be `Hex.encodeHexString(wideVineId)`, which gives you a 64-character `String` in the specific case mentioned above (a bit more or less depending on the phone). If you want an even shorter `String`, you could hash it. The `SHA-256` hash mentioned in the answer by @Sofien is not the most concise, you might want to look for more concise hashes, see also [this question](https://stackoverflow.com/questions/7557173/is-there-a-way-to-shorten-a-java-security-messagedigest-generated-value). – MathMax Mar 11 '20 at 16:38
  • Thanks, I will check it out! – jvargas Mar 11 '20 at 16:41
  • You can make shorter value by converting the byte array into Base64 String like Base64.encodeToString(WideVineId , Base64.Default) – user1154390 Jul 20 '20 at 14:34
  • "Does WIDEVINE_UUID survives factory reset or not?" Any documentation about that? (I search, but could not find any resource about it) – Pascal Aug 14 '22 at 12:26
  • Does not survive factory reset on my side... Am I the only one? – Pascal Sep 08 '22 at 16:19
3
  1. On a device first boot, a random value is generated and stored. This value is available via Settings.Secure.ANDROID_ID. It’s a 64-bit number that should remain constant for the lifetime of a device. ANDROID_ID seems a good choice for a unique device identifier because it’s available for smartphones and tablets. To retrieve the value, you can use the following code,
String androidId = Settings.Secure.getString(getContentResolver(),
                                             Settings.Secure.ANDROID_ID);

However, the value may change if a factory reset is performed on the device. There is also a known bug with a popular handset from a manufacturer where every instance has the same ANDROID_ID. Clearly, the solution is not 100% reliable.

  1. Use UUID. As the requirement for most of the applications is to identify a particular installation and not a physical device, a good solution to get the unique id for a user if to use UUID class. The following solution has been presented by Reto Meier from Google in a Google I/O presentation,
SharedPreferences sharedPrefs = context.getSharedPreferences(
PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
Kiran Maniya
  • 8,453
  • 9
  • 58
  • 81
0

MEDIADRM API is one can use

//From Exo player

val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
    val id = MediaDrm(WIDEVINE_UUID)
        .getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
    var encodedString: String = Base64.encodeToString(id,Base64.DEFAULT)
    Log.i("Uniqueid","Uniqueid"+encodedString)
Ravindra Kushwaha
  • 7,846
  • 14
  • 53
  • 103
Vinayak
  • 6,056
  • 1
  • 32
  • 30
-4

I have tested it in Nokia phone "the identifier is changed when I reset my phone on factory reset". Did you test it on factory reset?

Elletlar
  • 3,136
  • 7
  • 32
  • 38
  • Your answer do not make sense. Use punctuation and maintain some grammatical structure.Writing any answer that can not even qualify as decent line is appalling. – Anklon Aug 19 '20 at 13:06
  • I have modified the answer what I mean is the identifier number changed if you make factory reset to the phone do you test it for factory reset – Yaser Yousef Aug 19 '20 at 13:31
  • Use proper "punctuation" in your answer and comment. – Anklon Aug 19 '20 at 13:33