APP中满足隐私合规获取设备唯一ID(DeviceID)

47 阅读4分钟

设备ID在我们日常业务追踪中和日志排查中起到了至关重要的作用,所以能在不同APP中、APP更新、卸载重装、手机重启、手机恢复默认设置后还能保持唯一就显得很有必要了,下面我们首先我们介绍下常用获取设备id的方法,以及他们之间的差异。

Android获取设备id的方式:

iOS中获取设备id的方式:

通过上面两张表格我们明显发现安卓的MAC地址、IMEI/MEID和ios的UDID是我门想要的不二之选;

不过很可以ios中UDID私有方法以及被苹果禁用,不允许上架使用了,所以无法使用;

**安卓的MAC地址、IMEI/MEID自从《个人信息保护法》发布和隐私合规限制,也不允许使用了;
**

那么留给我们的发挥的空间就很小了,基本上余下都在各个场景中会产生变化,这对我们的日志追踪会造成毁灭性的破坏,所以我门需要重新寻找替代方案,最小程度上杜绝设备id变化。

1、在iOS中我们可以使用钥匙串实现

我们想到可以使用UUID/IDFV生产一个临时的id,然后将其存储到系统的钥匙串中,在使用的时候从钥匙串中取值这样就能保持不同APP之间的设备ID唯一,同时不管APP卸载还是重启设备ID都不会变化。

当然这样也不是完全安全的,如果我们认为恢复出厂设置、重装系统等还是会删除钥匙串中的信息,只不过我们是将设备ID变化的可能降到最低。

代码

**@implementation LGShareUdidTool
**

  • (instancetype)shareManager{

    staticLGShareUdidTool* manager;

    staticdispatch_once_tonceToken;

    dispatch_once(&onceToken, ^{

        manager = [[LGShareUdidToolalloc]init];

    });

    returnmanager;

}

  • (NSString *) getDeviceUDID {

    return  [[LGShareUdidTool shareManager] getDeviceUDID];

}

  • (NSString *) getDeviceUDID {

    //中间变量防止多次读取 影响性能

    if(self.deviceUdid.length>0) {

        returnself.deviceUdid;

    }

    if ([self getKeychainDataForKey:kUdidKeychain]) {

        self.deviceUdid = [self getKeychainDataForKey:kUdidKeychain];

        if(self.deviceUdid.length>0) {

            returnself.deviceUdid;

        }

        //如果有脏数据 则重新写入

        self.deviceUdid = [UIDevice currentDevice].identifierForVendor.UUIDString;

        [self addKeychainData:self.deviceUdid forKey:kUdidKeychain];

        returnself.deviceUdid;

    }else{

        //写入

        self.deviceUdid = [UIDevice currentDevice].identifierForVendor.UUIDString;

        [self addKeychainData:self.deviceUdid forKey:kUdidKeychain];

        returnself.deviceUdid;

    }

}

//创建一个基本的查询字典

  • (NSMutableDictionary *)getKeychainQuery:(NSString *)service {

    return [NSMutableDictionary dictionaryWithObjectsAndKeys:

            (__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass,

            service, (__bridgeid)kSecAttrService,

            service, (__bridgeid)kSecAttrAccount,

            (__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible,

            nil];

}

  • (id)getKeychainDataForKey:(NSString *)key{

    idret =nil;

    NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];

    //Configure the search setting

    //Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue

    [keychainQuerysetObject:(id)kCFBooleanTrueforKey:(__bridgeid)kSecReturnData];

    [keychainQuerysetObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];

    CFDataRefkeyData =NULL;

    if(SecItemCopyMatching((__bridgeCFDictionaryRef)keychainQuery, (CFTypeRef*)&keyData) ==noErr) {

        @try{

            ret = [NSKeyedUnarchiverunarchiveObjectWithData:(__bridgeNSData*)keyData];

        }@catch(NSException *e) {

            NSLog(@"Unarchive of %@ failed: %@",key,e);

        }@finally{

        }

    }

    if(keyData)

        CFRelease(keyData);

    returnret;

}

  • (void)addKeychainData:(id)dataforKey:(NSString*)key{

    //Get search dictionary

    NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];

    //Delete old item before add new item

    SecItemDelete((__bridgeCFDictionaryRef)keychainQuery);

    //Add new object to search dictionary(Attention:the data format)

    [keychainQuerysetObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];

    //Add item to keychain with the search dictionary

    SecItemAdd((__bridgeCFDictionaryRef)keychainQuery,NULL);

}

  • (void)addShareKeyChainData:(id)data forKey:(NSString *)key{

    //Get search dictionary

    NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];

    [keychainQuerysetObject:kAccessGroupItemforKey:(id)kSecAttrAccessGroup];

    //Delete old item before add new item

    SecItemDelete((__bridgeCFDictionaryRef)keychainQuery);

    //Add new object to search dictionary(Attention:the data format)

    [keychainQuerysetObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];

    //Add item to keychain with the search dictionary

    SecItemAdd((__bridgeCFDictionaryRef)keychainQuery,NULL);

}

  • (void)deleteKeychainDataForKey:(NSString *)key{

    NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:key];

    SecItemDelete((__bridgeCFDictionaryRef)keychainQuery);

}

**2、在安卓中我们使用卓信ID
**

在合规中UDID、MAC地址被严格限制获取,在这里我门找到中国信通院提供的卓信ID,用来合规的替代获取设备的ID,感兴趣的可以自行去了解一下。

不过卓信ID也并不是一成不变的,卓信id通过手机硬件信息生成一个设备的唯一根id,再根id基础上暴露一个卓信id给用户,卓信id一个月更新一次。卓信id提供两个接口,一个是通过卓信id查设备上六个月内所有的历史卓信id,一个是对比六个月内任意两个id是否来至于同一设备,同时卓信ID也可能会获取失败。

在隐私合规中获取oaid(安卓11系统以上)是在允许范围内的,所以我们增加oaid和友盟ID来弥补卓信ID获取失败时的兜底

1、冷启动时调获取DeviceID接口,返回有效时间和DeviceID,本地缓存DeviceID

2、如果下次冷启动时效过了就重新请求DeviceID接口,否者不用重新请求

3、如果接口请求失败,重连一次,降低因为网络或者服务波动引起的接口请求失败,导致id获取失败

4、如果卓信id、oaid为空不必生成DeviceID,经过验证这种概率很低了。