UUID/UDID/IDFA/IDFV是啥
UDID (Unique Device Identifier)
从名字看就是唯一设备标识符但是这个玩意在iOS5中被苹果禁掉了,所以基本不讨论了.
顺带一提,iOS6中禁掉了MAC地址的获取,所以也基本不讨论了.
UUID(Universally Unique Identifier)
通用唯一识别码,可以通过IDFA和IDFV进行生成,每次一旦重新生成就会改变.所以现在大多把其生成结果保存在钥匙串KeyChain里,因为KeyChain是伴随着设备或者iCloud的.
IDFA (Identifier For Advertising)
广告标识符,Apple公司用于追踪广告,可以用于生成UUID
- 缺点:用户可通过”设置-隐私-广告-还原广告标识符”还原,之后会得新的到标识符
- 系统要求: iOS>=6.0
获取方法
#import <AdSupport/AdSupport.h>
NSString *idfa= [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
IDFV (Identifier For Vendor)
开发商标识符,Vendor是小贩的意思,Apple 的意思是你们开发者都是小贩,我是城管,具体Vendor是指CFBundleIdentifier的前两部分,例如 com.baidu.tieba 和 com.baidu.image 得到的IDFV是相同的,因为它们的CFBundleIdentifier 前两部分是相同的.
缺点:把同一个开发商的所有应用全卸载后,再次安装取到的IDFV会不同。注意是所有应用,比如百度贴吧和百度云
要求:iOS>=6.0
获取方法
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
唯一标识符策略
目前通用的唯一标识符策略是在某种UUID生成后(通过IDFV/IDFA生成),保存至KeyChain和自定义剪切版
- KeyChain目的: 即使标识符被还原了,设备不清空KeyChain读取,就可以从KeyChain读取.
- 剪贴板目的: 有时写入KeyChain会因为各种原因失败,作为一种保险措施
但是即使这样,也不能保证设备标识永久不变,存在以下情况
- 用户并没有登录iCloud或者iCloud并没有开启钥匙串同步
- 用户进行了刷机或者通过卸载应用重置广告位等清空了一次UUID
以上两种情况同时出现,那么同一台设备就会被标识成一台新设备
唯一标识符代码
@interface DeviceIDTool : NSObject
//设备指纹ID
+ (NSString *)deviceUUID;
@end
#import "DeviceIDTool.h"
#import "KeychainItemWrapper.h"
#import <CommonCrypto/CommonDigest.h>
NSString *const kDeviceUUID = @"com.baidu.tieba.uuid";//UUID在KeyChain的Key值
NSUInteger const md5CodeLength = 5;//摘要位数
NSUInteger const firstLength = 8;//片段一加密长度
NSUInteger const secondLength = 10;//片段二加密长度
@implementation DeviceIDTool
//取出UUID
+ (NSString *)deviceUUID {
NSString *deviceId = [self deviceID] ;
if(deviceId.length-md5CodeLength>0) {
return [deviceId substringToIndex:deviceId.length-md5CodeLength] ;
}
return deviceId ;
}
//生成UUID
+ (NSString *)deviceID {
NSString *deviceID = nil ;
//生成KeyChain,取出ID的数据
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:kDeviceUUID accessGroup:nil] ;
NSString *deviceID1 = [keychainItem objectForKey:(id)kSecValueData] ;
//生成剪切版,取出剪贴板数据
NSString *deviceID2 = nil ;
UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:kDeviceUUID create:YES] ;
NSData *data = [pasteboard valueForPasteboardType:kDeviceUUID] ;
if(data && [data isKindOfClass:[NSData class]]) {
deviceID2 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
//去过取出为空,则进行生成,并放入剪贴板和KeyChain,函数返回
if (deviceID1.length==0 && deviceID2.length==0) {
deviceID = [self generatorDeviceId] ;
[keychainItem setObject:kDeviceUUID forKey:(id)kSecAttrAccount];
[keychainItem setObject:deviceID forKey:(id)kSecValueData];
[pasteboard setValue:deviceID forPasteboardType:kDeviceUUID] ;
return deviceID ;
}
//成功取出,检查UUID是否符合自定义摘要逻辑
BOOL deviceIdAvailable1 = [self checkDeviceIdAvailable:deviceID1] ;
BOOL deviceIdAvailable2 = [self checkDeviceIdAvailable:deviceID2] ;
//生成Current UUID
NSString *uniqueIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
//检查从KeyChain取出的UUID是否包含Current UUID
if([deviceID1 hasPrefix:uniqueIdentifier]) {
[pasteboard setValue:deviceID1 forPasteboardType:kDeviceUUID] ;
return deviceID1 ;
}
//检查从粘贴板取出的UUID是否包含Current UUID
if([deviceID2 hasPrefix:uniqueIdentifier]) {
[keychainItem setObject:kDeviceUUID forKey:(id)kSecAttrAccount];
[keychainItem setObject:deviceID2 forKey:(id)kSecValueData];
return deviceID2 ;
}
//KeyChain和剪贴板都符合自定义逻辑
if(deviceIdAvailable1 && deviceIdAvailable2) {
//KeyChain和剪贴板相同,返回KeyChain
if ([deviceID1 isEqualToString:deviceID2]) {
return deviceID1 ;
}
else {
//KeyChain和剪贴板不同,但是却同时都符合逻辑
[keychainItem setObject:kDeviceUUID forKey:(id)kSecAttrAccount];
[keychainItem setObject:deviceID2 forKey:(id)kSecValueData];
return deviceID2 ;
}
}
//KeyChain符合逻辑,粘贴板不符合逻辑,返回KeyChain并重写剪贴板
else if (deviceIdAvailable1 && !deviceIdAvailable2) {
[pasteboard setValue:deviceID1 forPasteboardType:kDeviceUUID] ;
return deviceID1 ;
}
//KeyChain不符合逻辑,剪贴板符合逻辑,返回剪贴板,重写KeyChain
else if (!deviceIdAvailable1 && deviceIdAvailable2) {
[keychainItem setObject:kDeviceUUID forKey:(id)kSecAttrAccount];
[keychainItem setObject:deviceID2 forKey:(id)kSecValueData];
return deviceID2 ;
}
else {
//都不符合逻辑,于是重新生成新的
deviceID = [self generatorDeviceId] ;
[keychainItem setObject:kDeviceUUID forKey:(id)kSecAttrAccount];
[keychainItem setObject:deviceID forKey:(id)kSecValueData];
[pasteboard setValue:deviceID forPasteboardType:kDeviceUUID] ;
return deviceID ;
}
return nil ;
}
//通过IDFV取出UUID
+ (NSString *)generatorDeviceId {
NSString *uniqueIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
return [self encryptUUID:uniqueIdentifier] ;
}
//自定义摘要逻辑,取两个片段进行md5,然后合并md5结果再来一次
//取UUID然后拼接md5CodeLength长度的摘要
+ (NSString *)encryptUUID:(NSString *)uuid {
if(uuid.length>20) {
NSString *subString1 = [uuid substringToIndex:firstLength] ;
NSString *subString2 = [uuid substringToIndex:secondLength] ;
subString1 = [self md5Encrypt:subString1] ;
subString2 = [self md5Encrypt:subString2] ;
//合并之后再来一次
NSString *combineString = [subString1 stringByAppendingString:subString2];
NSString *md5 = [self md5Encrypt:combineString] ;
if(md5.length >= md5CodeLength) {
md5 = [md5 substringToIndex:md5CodeLength-1] ;//取前md5CodeLength位
return [uuid stringByAppendingFormat:@"-%@", md5] ;//加入原文UUID后
}
}
return uuid ;
}
//验证合法性
+ (BOOL)checkDeviceIdAvailable:(NSString *)deviceId {
if(deviceId.length>md5CodeLength) {
//取出UUID中系统生成的部分,进行自定义逻辑计算
NSString *uuid = [deviceId substringToIndex:deviceId.length-md5CodeLength] ;
NSString *encrypt = [self encryptUUID:uuid] ;
//检查经过自定义摘要逻辑后是否还是原字符串
//符合即为合法的,且符合自定义逻辑
if ([encrypt isEqualToString:deviceId] && ![encrypt isEqualToString:uuid]) {
return YES ;
}
}
return NO ;
}
+ (NSString *)md5Encrypt:(NSString *)string {
const char *original_str = [string UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(original_str, (unsigned int)strlen(original_str), result);
NSMutableString *hash = [NSMutableString string];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02X", result[i]];
}
return hash;
}
@end
唯一标识符被冒用的可能性
假设正常用户A下载APP生成了UUID1 = UUIDA+MD5A,根据逻辑存入了A的iCloud-KeyChain.存入时使用了开发者自定义的Key(kDeviceUUID),和根据自定义逻辑(encryptUUID)生成的Value.开发者B的AppB使用这个UUID1来认证正常用户A
此时冒用者C在自己设备上想冒充正常用户A,由于C无法接触到A的设备,在AppB中的
+ (NSString *)deviceID {
...
NSString *uniqueIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
...
}
由于重新生成了UUID,不可能和正常用户A的UUID1一样,所以此时为UUID2.
冒用者此时需要把自己KeyChain中的Key = kDeviceUUID 对应的Value值改为UUID1,且名称为kDeviceUUID粘贴板中的值也改为UUID1才能冒充成正常用户A
所以说冒用者C必须知道正常用户的iCloud帐号且知道对应的Key = kDeviceUUID 才能定位到UUID1
如果正常用户A没有同步KeyChain,那么必须接触到用户的设备,才有可能知晓UUID1.
结论
极端情况下UUID是可以被冒充的,但是必须同时掌握
- App账户
- iCloud账户
- App存储所用Key值
才能触及用户信息,基本能同时掌握这三个的也只有用户本人了.