Unique Identification of Android Devices (Multiple Implementation Solutions)

Posted by chings on Fri, 11 Oct 2019 05:47:17 +0200

Preface

In project development, how many of these requirements will be met: to obtain device Id, which is the unique identifier of the device, for:
1. Identify a unique device and do accurate data dissemination or data statistical analysis;
2. Account and equipment binding;
3.....

Analysis

This kind of article, there are many information on the internet, such as: using IMEI, MAC and so on as device identification.
However, students who have read these articles or conducted in-depth research should be aware that these data are flawed: some are not accessible because of the privileges, some are duplicated, and some are completely inaccessible, that is to say, the only problem of the device can not be solved perfectly.

So, what data can be used to indicate that the device is unique?

programme

Solution 1: UUID + SharePreference (Access)
When APP is first used, create a UUID and save it in SharePreference.
When it is used again later, it can be taken out directly from SharePreference.

Advantages: data is unique and does not require permission;
Disadvantage: It will be deleted along with APP, that is, to reinstall APP, DeviceId value will change (new UUID);
Scheme 2: UUID + SD Card (Access)
When APP is first used, create UUID and save it to SD card.
When it is used again later, it can be taken out directly from the SD card.
Many APP s do this.

Advantages: Data is unique and not deleted with APP;
Disadvantages: SD card read and write permission is required; users can not prevent the manual deletion of SD card files;
Scheme 3: imei + android_id + serial + hardware uuid (self-generated)

What if you want to be unique and do not want to regenerate UUID because of user deletion?

Instead of relying on random UUID s, we create unique data based on hardware identifiers.

We can mosaic multiple available hardware identities (as far as possible without relying on privileges) to minimize repeatability.
Take imei, android_id, serial for example, if the value can be obtained, each data can represent almost unique.
If these data can be obtained, the repeatability of the assembled data is reduced to a very low level (UUID also has repeatability, repeatability is very low only).

So what hardware identifiers are appropriate?

Android Id: For example: df176fbb152ddce, without permission, very few devices can not get data or error data;
Series: For example: LKX7N18328000931, without permission, very few devices can not obtain data;
IMEI: For example: 23b12e30ec8a2f17, need permission;
Mac: For example: 6e:a5:.... Require permission, high-level mobile phone access data are 02:00.... (Not available)
Build.BOARD for example: BLA motherboard name, without permission, the same type of equipment
 Build.BRAND for example: HUAWEI manufacturer name, no permission required, the same type of equipment
 Build.HARDWARE such as: kirin970 hardware name, no permission, the same type of equipment
 Build... More hardware information, Brief

After analyzing so many hardware identifiers, we use imei + android_id + serial + hardware UUID (generated using Build attribute, if the hardware information remains unchanged, the UUID value remains unchanged). This is the actual plan of our project. We can also combine hardware identification freely according to our own needs.

Then, the problem arises again. Different devices have different hardware identification lengths and different device Id string lengths for splicing. How can we unify the lengths?

It's also very simple. First we splice the DeviceId data, take its SHA1 value, and then turn to 16 (uniform 40-bit length)

Realization

import android.content.Context;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;

import java.security.MessageDigest;
import java.util.Locale;
import java.util.UUID;

/**
 * @author xc
 * @date 2018/11/16
 * @desc
 */
public class DeviceIdUtil {
    /**
     * Obtain device hardware identification
     *
     * @param context context
     * @return Equipment Hardware Identification
     */
    public static String getDeviceId(Context context) {
        StringBuilder sbDeviceId = new StringBuilder();

        //Getting device default IMEI (>= 6.0 requires ReadPhoneState privileges)
        String imei = getIMEI(context);
        //Get Android Id (no privileges)
        String androidid = getAndroidId(context);
        //Get device serial number (without permission)
        String serial = getSERIAL();
        //Get the hardware uuid (generate uuid according to hardware-related properties) (no permission required)
        String uuid = getDeviceUUID().replace("-", "");

        //Append imei
        if (imei != null && imei.length() > 0) {
            sbDeviceId.append(imei);
            sbDeviceId.append("|");
        }
        //Additional androidid
        if (androidid != null && androidid.length() > 0) {
            sbDeviceId.append(androidid);
            sbDeviceId.append("|");
        }
        //Append serial
        if (serial != null && serial.length() > 0) {
            sbDeviceId.append(serial);
            sbDeviceId.append("|");
        }
        //Additional hardware uuid
        if (uuid != null && uuid.length() > 0) {
            sbDeviceId.append(uuid);
        }

        //Generate SHA1, Unify DeviceId Length
        if (sbDeviceId.length() > 0) {
            try {
                byte[] hash = getHashByString(sbDeviceId.toString());
                String sha1 = bytesToHex(hash);
                if (sha1 != null && sha1.length() > 0) {
                    //Return to the final EviceId
                    return sha1;
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        //If the above hardware identification data are not available,
        //DeviceId defaults to system random numbers, which ensures that DeviceId is not empty
        return UUID.randomUUID().toString().replace("-", "");
    }

    //Require READ_PHONE_STATE permission, >= 6.0, return null by default
    private static String getIMEI(Context context) {
        try {
            TelephonyManager tm = (TelephonyManager) 
context.getSystemService(Context.TELEPHONY_SERVICE);
            return tm.getDeviceId();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return "";
    }

    /**
     * Android Id for Getting Devices
     *
     * @param context context
     * @return Android Id for devices
     */
    private static String getAndroidId(Context context) {
        try {
            return Settings.Secure.getString(context.getContentResolver(), 
Settings.Secure.ANDROID_ID);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return "";
    }

    /**
     * Getting the serial number of the device (e.g. WTK7N16923005607), individual devices are not available.
     *
     * @return Equipment serial number
     */
    private static String getSERIAL() {
        try {
            return Build.SERIAL;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return "";
    }

    /**
     * Obtain device hardware uuid
     * Calculate a random number using hardware information
     *
     * @return Equipment hardware uuid
     */
    private static String getDeviceUUID() {
        try {
            String dev = "3883756" +
                    Build.BOARD.length() % 10 +
                    Build.BRAND.length() % 10 +
                    Build.DEVICE.length() % 10 +
                    Build.HARDWARE.length() % 10 +
                    Build.ID.length() % 10 +
                    Build.MODEL.length() % 10 +
                    Build.PRODUCT.length() % 10 +
                    Build.SERIAL.length() % 10;
            return new UUID(dev.hashCode(), 
Build.SERIAL.hashCode()).toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    /**
     * Take SHA1
     * @param data data
     * @return Corresponding hash value
     */
    private static byte[] getHashByString(String data)
    {
        try{
            MessageDigest  messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.reset();
            messageDigest.update(data.getBytes("UTF-8"));
            return messageDigest.digest();
        } catch (Exception e){
            return "".getBytes();
        }
    }

    /**
     * Conversion to hexadecimal string
     * @param data data
     * @return 16 Hexadecimal Strings
     */
    private static String bytesToHex(byte[] data){
        StringBuilder sb = new StringBuilder();
        String stmp;
        for (int n = 0; n < data.length; n++){
            stmp = (Integer.toHexString(data[n] & 0xFF));
            if (stmp.length() == 1)
                sb.append("0");
            sb.append(stmp);
        }
        return sb.toString().toUpperCase(Locale.CHINA);
    }
}

call

String deviceId = DeviceIdUtil.getDeviceId(application);

//Output: FE00DDE9298310CDFEEFE69229B8DB248534710F

summary

Scheme 1 is not recommended because of its limitations.
Solution 2 is a solution adopted by many software, because few people delete SD card files, but need to pay attention to permissions;
Compared with the first two schemes, scheme 3 has less restriction, so long as the hardware information remains unchanged, the result will remain unchanged. Moreover, the scheme can be customized.

What kind of scheme is suitable? We should choose it reasonably according to our own project needs.

Topics: Android SHA1 Java Mac