谈谈iOS 10.3后DeviceID保持唯一的方式

2017/03/24 iOS

源码:MFSIdentifier

前段时间介绍过DeviceID安全的获取方式, 保存DeviceID在系统升级后都能正常获取的重点就是KeyChain,在iOS 10.3的beta版本中,apple对KeyChain进行的改动,哪怕现在KeyChain能正常获取,我们也要想一些办法升级下deviceID的存取。

/** 升级后获取设备标识符
 safariCookie -> cacheManager -> iCloud -> IDFA -> IDFV -> pushToken
 iCloud支持需开启Key-value storage
 */
+ (NSString *)deviceId;

具体的实现伪代码

NSString *const kMFSIdentifierCacheDeviceIdKey = @"com.imfong.Device.cacheKey";
NSString *const kMFSIdentifierUserDeviceIdKey = @"com.imfong.deviceId.defaultkey";
NSString *const kMFSIdentifierKeyChainKey = @"com.imfong.deviceId.KeyChain.Key";

+ (NSString *)deviceId
{
    NSString *cacheDeviceId = [cacheManager() objectForKey:kMFSIdentifierCacheDeviceIdKey];
    NSString *userDeviceId = [userDefaults() objectForKey:kMFSIdentifierUserDeviceIdKey];
    //缓存和UserDefaults都是存储在本地,数据存在说明已经非首次安装,数据相同说明是正常用户
    if (cacheDeviceId.length && userDeviceId.length && [cacheDeviceId isEqualToString:userDeviceId]) {
        NSString *cookieDId = [MFSSafariCookieDeviceId cookieDId]; //基于缓存存储,开启APP时肯定返回nil
        //cookieDID存在,则以cookieDID为准(cookieDID值会进行AES解密,理论上安全可信)
        if (cookieDId.length && ![cookieDId isEqualToString:cacheDeviceId]) {
            return cookieDId;
        }
        return cacheDeviceId;
    }
    else {
        NSString *keyChain_Key = kMFSIdentifierKeyChainKey;
        NSString *deviceId = [MFSKeyChain objectForKey:keyChain_Key];
        if (!deviceId.length) {
            //SafariCookieDeviceId首次获取必定是nil,写在此处是为了容错
            deviceId = [MFSSafariCookieDeviceId cookieDId];
            if (!deviceId.length) {
                //走到这步说明UserDefaults的plist被人为删除,获取客户端内缓存数据
                deviceId = cacheDeviceId;
                if (!deviceId.length) {
                    //客户端内缓存数据也被清理,获取iCloud数据
                    deviceId = [[NSUbiquitousKeyValueStore defaultStore] objectForKey: kMFSIdentifierUserDeviceIdKey];
                    if (!deviceId.length) {
                        //iCloud未登陆或者被清空,获取IDFA
                        if ([[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]) {
                            deviceId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
                        }
                        if (!deviceId.length) {
                            //IDFA被用户手动重置,取IDFV
                            deviceId = [[UIDevice currentDevice] identifierForVendor].UUIDString;
                            if (!deviceId.length) {
                                //什么都取不到,获取token
                                deviceId = @"devicePushToken值";
                                if (!deviceId.length) {
                                    deviceId = [[NSUUID UUID] UUIDString];
                                }
                            }
                        }
                    }
                }
            }
        }
        if (deviceId.length) {
            [MFSKeyChain setObject:deviceId forKey:keyChain_Key];
            [cacheManager() setObject:deviceId forKey:kMFSIdentifierCacheDeviceIdKey duration:-9];
            [userDefaults() setObject:deviceId forKey:kMFSIdentifierUserDeviceIdKey];
            [[NSUbiquitousKeyValueStore defaultStore] setString:deviceId forKey: kMFSIdentifierUserDeviceIdKey];
            [[NSUbiquitousKeyValueStore defaultStore] synchronize];
            
        }
        return deviceId ?: @"";
    }
}

#pragma mark -
static MFSCacheManager *cacheManager() {
    static id instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[MFSCacheManager alloc] initWithSuiteName:@"com.imfong.Identifier.Cache"];
    });
    return instance;
}

static NSUserDefaults *userDefaults() {
    static id instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[NSUserDefaults alloc] initWithSuiteName:@"com.imfong.Identifier.userDefaults"];
    });
    return instance;
}

SafariCookie的获取和存储伪代码:

@implementation MFSSafariCookieDeviceId

+ (void)setCookieDId:(NSString *)idd {
    if (idd.length) {
    	//传出去的是AES加密值,理论上安全
        NSString *deviceId = [[idd AESEncryptAndBase64Encode] URLEncodedString];
        NSString *url = [NSString stringWithFormat:@"%@?value=%@", @"https://www.imfong.com/app/device", deviceId];
        
        //通过SFSafariViewController打开链接(使用addChildViewController的形式并设置view.frame为{0, 0, 1, 1})并通过JS唤起app
        //window.location.href = "mfsApp://imfong/app/device?deviceId="+(value的值);
    }
}
+ (NSString *)cookieDId {
    return [cacheManager() objectForKey:@"value"];
}

@end

只剩下最后一次,app的scheme内的处理

- (void)scheme的拦截后的deviceID处理方法:(NSString *)value {
    if (系统版本大于iOS9(SFSafariViewController iOS9开始支持)) {
    	//浏览器传回来的值解密,能正确解密则表示数据有效
        NSString *deviceId = [[value URLDecodedString] AESDecryptAndBase64Decode];
        
        if (!deviceId.length) {
        	//此处触发一次cookie存储
        	//scheme为异步形式,客户端内其他时机无法直接触发
            deviceId = [MFSIdentifier deviceId];
            [MFSSafariCookieDeviceId setCookieDId:deviceId];
        }
        //存到MFSSafariCookieDeviceId供cookieDId使用
        [cacheManager() setObject:deviceId forKey:@"value" duration:-1];
        
        //已安装应用的设备在+deviceid方法中,不会触发NSUbiquitousKeyValueStore,所以需要在另外一个地方触发存储
        [[NSUbiquitousKeyValueStore defaultStore] setString:deviceId forKey:@"com.imfong.deviceId.key...."];
        [[NSUbiquitousKeyValueStore defaultStore] synchronize];
    }
}

其他

大概的流程就是这样,细节部分有疑惑的可以再探讨。
比较重要的就是加密解密部分,如果加密不靠谱或者已经泄露,deviceid可能会被随意改动。
多个app通过cookie能保持deviceid共享,获取到唯一deviceid。

Error: Comments Not Initialized

Search

    Post Directory