近期開發(fā)中有一個生成設(shè)備指紋的需求姻氨,什么是設(shè)備指紋呢?簡單講剪验,就是設(shè)備的唯一標識肴焊,用來標識某一設(shè)備。那么這個值功戚,在通常情況下娶眷,應(yīng)該是唯一且不可變的。iOS 系統(tǒng)提供了一些字段啸臀,但是單一使用這些字段并不能完全滿足需求届宠,下面對這些字段做些介紹。
一乘粒、UDID
1豌注、什么是 UDID
UDID 「Unique Device Identifier Description」是由子母和數(shù)字組成的40個字符串的序號,用來區(qū)別每一個唯一的 iOS 設(shè)備灯萍,包括 iPhone轧铁,iPad,以及 iPod touche旦棉,這些編碼看起來是隨機的齿风,實際上是跟硬件設(shè)備特點相聯(lián)系的药薯,另外你可以到 iTunes 等軟件查看設(shè)備的 udid。
2救斑、UDID 可以用來做什么
iOS 2.0 版本以后 UIDevice 提供一個獲取設(shè)備唯一標識符的方法 uniqueIdentifier童本,通過該方法我們可以獲取設(shè)備的序列號,這個也是目前為止唯一可以確認的設(shè)備的唯一標示符脸候。UDID 可以關(guān)聯(lián)其他各種數(shù)據(jù)到相關(guān)設(shè)備上穷娱。例如,連接到開發(fā)者賬號纪他,可以在 APP 發(fā)布前進行真機測試鄙煤;也可以讓開發(fā)者獲得 APP 測試版進行體驗;蘋果用 UDID 連接到蘋果的 ID茶袒,這些設(shè)備可以自動下載和安裝從 App Store 購買的應(yīng)用、保存從 iTunes 購買的音樂凉馆、幫助蘋果發(fā)送推送通知薪寓、即時消息等。 在 iOS 應(yīng)用早期澜共,UDID 被第三方應(yīng)用開發(fā)者和網(wǎng)絡(luò)廣告商用來收集用戶數(shù)據(jù)向叉,可以用來關(guān)聯(lián)地址、記錄應(yīng)用使用習(xí)慣等嗦董,以便推送精準廣告母谎。
3、獲取方法
NSString *udid = [[UIDevice cuurrent] uniqueIdenfier];
4京革、使用限制
許多開發(fā)者把UDID跟用戶的真實姓名奇唤、密碼、住址匹摇、其它數(shù)據(jù)關(guān)聯(lián)起來咬扇;網(wǎng)絡(luò)窺探者會從多個應(yīng)用收集這些數(shù)據(jù),然后順藤摸瓜得到這個人的許多隱私數(shù)據(jù)廊勃。同時大部分應(yīng)用確實在頻繁傳輸 UDID 和私人信息懈贺,為了避免集體訴訟,蘋果最終決定在 iOS 5 的時候坡垫,將這一慣例廢除梭灿,開發(fā)者被引導(dǎo)生成一個唯一的標識符,只能檢測應(yīng)用程序冰悠,其他的信息不提供”ざ剩現(xiàn)在應(yīng)用試圖獲取 UDID 已被禁止且不允許上架。
二屿脐、UUID
1涕蚤、什么是 UUID
UUID 英文名稱是:Universally Unique Identifier宪卿,翻譯過來就是通用唯一標識符。是一個32位的十六進制序列万栅,使用小橫線來連接:F8C6B3D6-0539-40BB-B26A-AD7D49E9C9D3佑钾。UUID 在某一時空下是唯一的。比如在當(dāng)前這一秒烦粒,全世界產(chǎn)生的 UUID 都是不一樣的休溶;當(dāng)然同一臺設(shè)備產(chǎn)生的 UUID 也是不一樣的。
2扰她、獲取方法
+ (void)UUIDString
{
NSString *uuid = [NSUUID UUID].UUIDString;
NSLog(@"uuid %@", uuid);
}
// 查看取值結(jié)果
uuid F8C6B3D6-0539-40BB-B26A-AD7D49E9C9D3
uuid 4EE64A18-F928-4FDF-A37D-FFAB5053A799
uuid BA2FADEC-C5D0-47DC-AB5B-09ECFD57532D
uuid 382B879E-9D0A-432B-9BC4-950AEEB36BE5
uuid 771A4087-C5DD-4D26-BB4F-272551B5B412
從打印結(jié)果可以看出兽掰,每次取到的 UUID 值是不一樣的,當(dāng)然徒役,無論取多少次值孽尽,永遠不會出現(xiàn)兩個一樣的結(jié)果。所以從某種程度上來說忧勿,UUID 跟設(shè)備已經(jīng)沒有什么關(guān)系了杉女。
三、IMEI
1鸳吸、什么是 IMEI
IMEI (International Mobile Equipment Identity) 是國際移動設(shè)備身份碼的縮寫熏挎,國際移動裝備辨識碼,是由15位數(shù)字組成的”電子串號”晌砾,它與每臺手機一一對應(yīng)坎拐,而且該碼是全世界唯一的。每一部手機在組裝完成后都將被賦予一個全球唯一的一組號碼养匈,這個號碼從生產(chǎn)到交付使用都將被制造生產(chǎn)的廠商所記錄哼勇。手機用戶可以在手機中查到自己手機的IMEI碼。
2乖寒、使用限制
iOS 2 提供了一種方法獲取 IMEI猴蹂,但是為了保護用戶隱私,iOS 5 以后蘋果不再允許獲取 IMEI 了楣嘁,如果你在應(yīng)用中獲取 IMEI磅轻,可能會遭到 Apple Store 的拒絕。
四逐虚、IDFA
1聋溜、什么是 IDFA
IDFA(Identifier for Identifier),即廣告標識符叭爱,多用于用戶的廣告追蹤撮躁,是每臺設(shè)備的唯一 ID,IDFA 存儲在用戶的系統(tǒng)上买雾。Apple 是不允許開發(fā)者追蹤用戶設(shè)備的把曼,但是為了監(jiān)控廣告效果杨帽,在 iOS 6 中提供這個折中方案,但是如果用戶還原設(shè)置與隱私的話這個廣告標識符就會重新生成(設(shè)置 -> 通用 -> 還原 -> 還原位置與隱私)或者用戶更明確的將設(shè)置中的廣告還原掉(設(shè)置程序 -> 通用 -> 關(guān)于本機 -> 廣告 -> 還原廣告標標識符)IDFA 也會重新生成嗤军。而且用戶可以在設(shè)置 -> 隱私 -> 廣告里面限制 IDFA 獲取注盈。
2、獲取方法
// 需要引入系統(tǒng)框架
import <AdSupport/AdSupport.h>
// 獲取方法
NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
五叙赚、IDFV
1老客、什么是 IDFV
IDFV(identifierForVendor):是給 Vendor 標識用戶用的,每個設(shè)備在所屬同一個Vender的應(yīng)用里震叮,都有相同的值胧砰。其中的Vender是指應(yīng)用提供商,但準確點說苇瓣,是通過BundleID的反轉(zhuǎn)的前兩部分進行匹配尉间,如果相同就是同一個Vender,例如對于 com.taobao.app1, com.taobao.app2 這兩個 BundleID 來說击罪,就屬于同一個 Vender乌妒,共享同一個 idfv 的值。和 idfa 不同的是外邓,idfv 的值是一定能取到的,所以非常適合于作為內(nèi)部用戶行為分析的主 id古掏,來標識用戶损话,替代 OpenUDID。
注意:如果用戶將屬于此 Vender 的所有 App 卸載槽唾,則 idfv 的值會被重置丧枪,即再重裝此 Vender 的 App,idfv 的值和之前不同庞萍。
2拧烦、獲取方法
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
IDFV 比較適合追蹤用戶行為,目前多配合 Keychain 來搭配使用钝计,來保證不管是否卸載重裝恋博,IDFV 都是不變的。
六私恬、MAC 地址
1债沮、什么是 MAC 地址
MAC(Medium/Media Access Control)地址,用來表示互聯(lián)網(wǎng)上每一個站點的標識符本鸣,采用十六進制數(shù)表示疫衩,共六個字節(jié)(48位)。其中荣德,前三個字節(jié)是由IEEE的注冊管理機構(gòu) RA負責(zé)給不同廠家分配的代碼(高位24位)闷煤,也稱為“編制上唯一的標識符” (Organizationally Unique Identifier)童芹,后三個字節(jié)(低位24位)由各廠家自行指派給生產(chǎn)的適配器接口,稱為擴展標識符(唯一性)鲤拿。
MAC 地址在網(wǎng)絡(luò)上用來區(qū)分設(shè)備的唯一性假褪,接入網(wǎng)絡(luò)的設(shè)備都有一個 MAC 地址,他們肯定都是不同的皆愉,是唯一的嗜价。一部iPhone上可能有多個 MAC 地址,包括 WIFI 的幕庐、SIM 的等久锥,但是 iTouch 和 iPad 上就有一個 WIFI 的,因此只需獲取 WIFI 的 MAC 地址就好了异剥,也就是en0的地址瑟由。
形象的說,MAC 地址就如同我們身份證上的身份證號碼冤寿,具有全球唯一性歹苦。這樣就可以非常好的標識設(shè)備唯一性,類似與蘋果設(shè)備的 UDID 號督怜,通常的用途有:1)用于一些統(tǒng)計與分析目的,利用用戶的操作習(xí)慣和數(shù)據(jù)更好的規(guī)劃產(chǎn)品号杠;2)作為用戶 ID 來唯一識別用戶蚪腋,可以用游客身份使用 APP 又能在服務(wù)器端保存相應(yīng)的信息,省去用戶名姨蟋、密碼等注冊過程屉凯。
但是,從 iOS7 及更高版本往后眼溶,如果你向 iOS 設(shè)備請求獲取 MAC 地址悠砚,系統(tǒng)將返回一個固定值 “02:00:00:00:00:00”,因為 MAC 地址跟 UDID 一樣堂飞,存在隱私問題灌旧。
2、獲取方法
// 引入一下庫文件
#import <sys/sysctl.h>
#import <net/if.h>
#import <net/if_dl.h>
// 實現(xiàn)代碼
+ (NSString *)getMacAddress {
int mib[6];
size_t len;
char *buf;
unsigned char *ptr;
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;
mib[0] = CTL_NET; mib[1] = AF_ROUTE;
mib[2] = 0; mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if ((mib[5] = if_nametoindex("en0")) == 0) {
printf("Error: if_nametoindex error/n");
return NULL;
}
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 1/n");
return NULL;
}
if ((buf = malloc(len)) == NULL) {
printf("Could not allocate memory. error!/n"); return NULL;
}
if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
free(buf);
printf("Error: sysctl, take 2");
return NULL;
}
ifm = (struct if_msghdr *)buf;
sdl = (struct sockaddr_dl *)(ifm + 1);
ptr = (unsigned char *)LLADDR(sdl);
// MAC 地址
NSString *outstring = [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
free(buf);
return [outstring uppercaseString];
}
七酝静、Keychain
為什么這里要提到 Keychain 呢节榜,因為上面介紹的所有字段,目前都不能完全實現(xiàn)對設(shè)備的唯一標識别智,因此宗苍,需要換種方式來滿足需求,即 IDFV + Keychain 的形式。
1讳窟、什么是 Keychain
Keychain Services 是 OS X 和 iOS 都提供一種安全地存儲敏感信息的工具让歼,比如,存儲用戶ID丽啡,密碼谋右,和證書等。存儲這些信息可以免除用戶重復(fù)輸入用戶名和密碼的過程补箍。Keychain Services 的安全機制保證了存儲這些敏感信息不會被竊取改执。
2、Keychain 的結(jié)構(gòu)
Keychain 可以包含任意數(shù)量的 keychain item坑雅。每一個 keychain item 包含數(shù)據(jù)和一組屬性辈挂。對于一個需要保護的 keychain item,比如密碼或者私鑰(用于加密或者解密的 string 字節(jié))會被 keychain 保護起來裹粤,數(shù)據(jù)是加密的终蒂;對于無需保護的 keychain item,例如遥诉,證書拇泣,數(shù)據(jù)不會被加密。
item可以指定為以下的類型:
extern CFTypeRef kSecClassGenericPassword
extern CFTypeRef kSecClassInternetPassword
extern CFTypeRef kSecClassCertificate
extern CFTypeRef kSecClassKey
extern CFTypeRef kSecClassIdentity OSX_AVAILABLE_STARTING(MAC_10_7, __IPHONE_2_0);
3矮锈、Keychain 的特點
- 數(shù)據(jù)并不存放在 App 的 Sanbox 中霉翔,即使刪除了 APP,資料依然保存在 keychain 中苞笨。如果重新安裝了APP早龟,還可以從 keychain 獲取數(shù)據(jù)。
- keychain 的數(shù)據(jù)可以用過 group 方式猫缭,讓程序可以在 APP 間共享,不過得要相同 TeamID壹店。
- keychain 的數(shù)據(jù)是經(jīng)過加密的猜丹。
4、Keychain 的使用
當(dāng)你想使用 keychain 進行一些操作硅卢,如保存一個密碼射窒,修改一個已存在 Keychain item 或者取回密碼等。Keychain 可以提供以下的操作:
- SecItemAdd 添加一個 item将塑;
- SecItemUpdate 更新已存在的 item脉顿;
- SecItemCopyMatching 搜索一個已存在的 item;
- SecItemDelete 刪除一個 keychain item点寥;
<1> 根據(jù)特定的 Service 創(chuàng)建一個用于操作 KeyChain 的 Dictionary
+ (NSMutableDictionary *)keyChainQueryDictionaryWithService:(NSString *)service
{
NSMutableDictionary *keyChainQueryDictaionary = [[NSMutableDictionary alloc]init];
[keyChainQueryDictaionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrService];
[keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrAccount];
return keyChainQueryDictaionary;
}
<2> 添加數(shù)據(jù)
+ (BOOL)addData:(id)data forService:(NSString *)service
{
NSMutableDictionary *keychainQuery = [self keyChainQueryDictionaryWithService:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
OSStatus status= SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
if (status == noErr) {
return YES;
}
return NO;
}
<3> 搜索數(shù)據(jù)
+ (id)queryDataWithService:(NSString *)service
{
id result;
NSMutableDictionary *keyChainQuery = [self keyChainQueryDictionaryWithService:service];
[keyChainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keyChainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keyChainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
result = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
}
@catch (NSException *exception) {
NSLog(@"不存在數(shù)據(jù)");
}
@finally {
}
}
if (keyData) {
CFRelease(keyData);
}
return result;
}
<4> 更新數(shù)據(jù)
+ (BOOL)updateData:(id)data forService:(NSString *)service
{
NSMutableDictionary *searchDictionary = [self keyChainQueryDictionaryWithService:service];
if (!searchDictionary) {
return NO;
}
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
[updateDictionary setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary, (CFDictionaryRef)updateDictionary);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
<5> 刪除數(shù)據(jù)
+ (BOOL)deleteDataWithService:(NSString *)service
{
NSMutableDictionary *keyChainDictionary = [self keyChainQueryDictionaryWithService:service];
OSStatus status = SecItemDelete((CFDictionaryRef)keyChainDictionary);
if (status == noErr) {
return YES;
}
return NO;
}
八艾疟、結(jié)語
到目前為止,想要僅僅通過 UUID、UDID蔽莱、Mac 地址等弟疆,來定位用戶設(shè)備已經(jīng)無法滿足需求,因為它們要么是無效的盗冷,要么是受到了 App Store 的限制怠苔。但是如果你想要追蹤廣告的話,可以使用 IDFA仪糖,想要用來分析用戶行為可以通過 IDFV + KeyChain 的方式來解決柑司。