How to Generate Unique Android Device Identifiers

Publication date:
Last update: 2021-05-30
Author:

 

Generating or obtaining a unique identifier for each device where an app is installed can be useful for different purposes. For example, in an online voting system, it can be interesting to avoid multiple votes using the same device. In transactional systems the identifiers can be used for traceability and correlation. This can also be used for license control purposes (ex: limiting the number of devices where a commercial app can be installed).

Unique Android device identifiers

 

The user's personal information, such as the identity number or telephone number, should not be used for these purposes due to privacy restrictions. The use of hardware identifiers, such as the MAC address or the IMEI number, is not recommended either. One of the reasons to avoid using hardware identifiers in Android has to do with the additional user permissions. Also, the IMEI number is not always unique. Some IMEI numbers do repeat, even though this is prohibited by the GSMA regulations.

There is no universal method to obtain a truly unique identifier in all Android devices. In this article we combine several of the existing methods to cover most use cases. Let's begin with the most used method, the "android_id":

long uid = 0;
String ss;
try{
   ss = Secure.getString(ctx, "android_id");
   if(ss != null){
      uid = new BigInteger(ss, 16).longValue();
      if(uid == 0x9774D56D682E549CL)
         uid = 0; // filter out the non-unique value
   }
}catch(Exception ex){
   Log.e("UID", "error", ex);
}
 

Note that this code continues execution in case of any unexpected errors, including numerical exceptions. If the value matches the hexadecimal 9774D56D682E549C, this value is discarded, because it's known that some devices can return the same value.

If the above code does not provide a non-zero value, we can try to obtain the Bluetooth MAC address, assuming the app has Bluetooth permissions:

// Since Android 10 the MAC can't be queried like this
if(uid == 0 && Build.VERSION.SDK_INT <= 28){
   ss = null;
   try{
      BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
      try{
         Field ff = btAdapter.getClass().getDeclaredField("mService");
         ff.setAccessible(true);
         Object oo = ff.get(btAdapter);
         ss = (String)oo.getClass().getDeclaredMethod("getAddress").invoke(oo);
      }catch(Throwable th){
         ss = btAdapter.getAddress();
      }
   }catch(Exception ex){
      Log.e("UID", "error", ex);
   }
   if(ss != null){

      // Convert the MAC to long
      int bb, vv = 0, xx = 0, yy = ss.length();
      boolean pp = false;
      while(xx < yy){
         if((bb = (ss.charAt(xx++) | 0x20) - 0x30) > 9)
            bb -= 0x27;
         if(bb >= 0 && bb <= 0xF){
            if(pp = !pp)
               vv = bb << 4;
            else
               uid = uid << 8 | vv | bb;
         }
      }

      // MAC not valid
      if((int)uid == 0 || uid <= 0 || uid > 0xFFFFFFFFFFFFL)
         uid = 0;
   }
}
 

If the above code also fails to get a good value, an alternative may be the exact time with milliseconds a known package was installed, such as "SystemUI". This value is not unique, but it can have reasonably good dispersion and persistence:

if(uid == 0){
   try{
      uid = new File(ctx.getPackageManager().getApplicationInfo(
         "com.android.systemui", 0).sourceDir).lastModified();
   }catch(Exception ex){
      Log.e("UID", "error", ex);
   }
}
 

A combination of these 3 methods may cover almost all use cases. To avoid collisions between the 3 or more methods, it's recommended to add a field to identify the method used.

Remember that not all phones have Bluetooth. Not all Android devices are phones and have mobile subscription and IMEI. Not all Android devices have the same default apps available. The broad diversity of Android devices creates interesting development challenges. By omitting compatibility with a specific device or group of devices, we can lose a market segment.