一.UDID(Unique Device Identifier)
UDID的全稱是Unique Device Identifier,它就是蘋果iOS設備的唯一識別碼,它由40位16進制數(shù)的字母和數(shù)字組成(越獄的設備通過某些工具可以改變設備的UDID)。移動網(wǎng)絡可利用UDID來識別移動設備,但是,從IOS5.0(2011年8月份)開始,蘋果宣布將不再支持用uniqueIdentifier方法獲取設備的UDID,iOS5以下是可以用的。蘋果從iOS5開始就移除了通過代碼訪問UDID的權限。從2013年5月1日起,試圖訪問UIDIDs的程序將不再被審核通過,替代的方案是開發(fā)者應該使用“在iOS 6中介紹的Vendor或Advertising標示符”。所以UDID是絕對是不能再使用了。
//UUID , 已廢除NSString*udid = [[UIDevice currentDevice] uniqueIdentifier];
1
2
1
2
為什么蘋果反對開發(fā)人員使用UDID?
iOS 2.0版本以后UIDevice提供一個獲取設備唯一標識符的方法uniqueIdentifier,通過該方法我們可以獲取設備的序列號,這個也是目前為止唯一可以確認唯一的標示符。 許多開發(fā)者把UDID跟用戶的真實姓名、密碼、住址、其它數(shù)據(jù)關聯(lián)起來;網(wǎng)絡窺探者會從多個應用收集這些數(shù)據(jù),然后順藤摸瓜得到這個人的許多隱私數(shù)據(jù)。同時大部分應用確實在頻繁傳輸UDID和私人信息。 為了避免集體訴訟,蘋果最終決定在iOS 5 的時候,將這一慣例廢除,開發(fā)者被引導生成一個唯一的標識符,只能檢測應用程序,其他的信息不提供。現(xiàn)在應用試圖獲取UDID已被禁止且不允許上架。
二.UUID(Universally Unique Identifier)
UUID是Universally Unique Identifier的縮寫,中文意思是通用唯一識別碼。它是讓分布式系統(tǒng)中的所有元素,都能有唯一的辨識資訊,而不需要透過中央控制端來做辨識資訊的指定。這樣,每個人都可以建立不與其它人沖突的 UUID。在此情況下,就不需考慮數(shù)據(jù)庫建立時的名稱重復問題。蘋果公司建議使用UUID為應用生成唯一標識字符串。
獲得的UUID值系統(tǒng)沒有存儲, 而且每次調用得到UUID,系統(tǒng)都會返回一個新的唯一標示符。如果你希望存儲這個標示符,那么需要自己將其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。
從iOS2.0開始,CFUUID就已經(jīng)出現(xiàn)了。它是CoreFoundatio包的一部分,因此API屬于C語言風格。CFUUIDCreate 方法用來創(chuàng)建CFUUIDRef,并且可以獲得一個相應的NSString,如下代碼:
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);NSString*cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
1
1
獲得的這個CFUUID值系統(tǒng)并沒有存儲。每次調用CFUUIDCreate,系統(tǒng)都會返回一個新的唯一標示符。如果你希望存儲這個標示符,那么需要自己將其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。
NSUUID在iOS 6中才出現(xiàn),這跟CFUUID幾乎完全一樣,只不過它是Objective-C接口。+ (id)UUID 是一個類方法,調用該方法可以獲得一個UUID。通過下面的代碼可以獲得一個UUID字符串:
NSString *uuid=[[NSUUID UUID] UUIDString];
1
1
跟CFUUID一樣,這個值系統(tǒng)也不會存儲,每次調用的時候都會獲得一個新的唯一標示符。如果要存儲的話,你需要自己存儲。在我讀取NSUUID時,注意到獲取到的這個值跟CFUUID完全一樣(不過也可能不一樣)
在iOS 5發(fā)布時,uniqueIdentifier被棄用了,這引起了廣大開發(fā)者需要尋找一個可以替代UDID,并且不受蘋果控制的方案。由此OpenUDID成為了當時使用最廣泛的開源UDID替代方案。OpenUDID在工程中實現(xiàn)起來非常簡單,并且還支持一系列的廣告提供商。
OpenUDID利用了一個非常巧妙的方法在不同程序間存儲標示符 — 在粘貼板中用了一個特殊的名稱來存儲標示符。通過這種方法,別的程序(同樣使用了OpenUDID)知道去什么地方獲取已經(jīng)生成的標示符(而不用再生成一個新的)。而且根據(jù)貢獻者的代碼和方法,和一些開發(fā)者的經(jīng)驗,如果把使用了OpenUDID方案的應用全部都刪除,再重新獲取OpenUDID,此時的OpenUDID就跟以前的不一樣。可見,這種方法還是不保險。
但是OpenUDID庫早已經(jīng)棄用了, 在其官方的博客中也指明了, 停止維護OpenUDID的原因是為了更好的向蘋果的舉措靠攏, 還指明了MAC Address不是一個好的選擇。
MAC(Medium/Media Access Control)地址,用來表示互聯(lián)網(wǎng)上每一個站點的標識符,采用十六進制數(shù)表示,共六個字節(jié)(48位)。其中,前三個字節(jié)是由IEEE的注冊管理機構 RA負責給不同廠家分配的代碼(高位24位),也稱為“編制上唯一的標識符” (Organizationally Unique Identifier),后三個字節(jié)(低位24位)由各廠家自行指派給生產的適配器接口,稱為擴展標識符(唯一性)。
MAC地址在網(wǎng)絡上用來區(qū)分設備的唯一性,接入網(wǎng)絡的設備都有一個MAC地址,他們肯定都是不同的,是唯一的。一部iPhone上可能有多個MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一個WIFI的,因此只需獲取WIFI的MAC地址就好了,也就是en0的地址。
形象的說,MAC地址就如同我們身份證上的身份證號碼,具有全球唯一性。這樣就可以非常好的標識設備唯一性,類似與蘋果設備的UDID號,通常的用途有:
1)用于一些統(tǒng)計與分析目的,利用用戶的操作習慣和數(shù)據(jù)更好的規(guī)劃產品;
2)作為用戶ID來唯一識別用戶,可以用游客身份使用app又能在服務器端保存相應的信息,省去用戶名、密碼等注冊過程。
主要分三種:
1、直接使用“MAC Address”
2、使用“MD5(MAC Address)”
3、使用“MD5(Mac Address+bundle_id)”獲得“機器+應用”的唯一標識(bundle_id 是應用的唯一標識)
iOS7之前,因為Mac地址是唯一的, 一般app開發(fā)者會采取第3種方式來識別安裝對應app的設備。為什么會使用它?在iOS5之前,都是使用UDID的,后來被禁用。蘋果推薦使用UUID 但是也有諸多問題,從而使用MAC地址。而MAC地址跟UDID一樣,存在隱私問題,現(xiàn)在蘋果新發(fā)布的iOS7上,如果請求Mac地址都會返回一個固定值,那么Mac Address+bundle_id這個值大家的設備都變成一致的啦,跟UDID一樣相當于被禁用, 所以Mac Address 是不能夠被使用為獲取設備唯一標識的。
五.廣告標示符(IDFA-identifierForIdentifier)
廣告標示符,在同一個設備上的所有App都會取到相同的值,是蘋果專門給各廣告提供商用來追蹤用戶而設的。但好在Apple默認是允許追蹤的,而且一般用戶都不知道有這么個設置,所以基本上用來監(jiān)測推廣效果,是戳戳有余了。
它是iOS 6中另外一個新的方法,提供了一個方法advertisingIdentifier,通過調用該方法會返回一個NSUUID實例,最后可以獲得一個UUID,由系統(tǒng)存儲著的。
#importNSString*adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
1
2
1
2
不過即使這是由系統(tǒng)存儲的,但是有幾種情況下,會重新生成廣告標示符。如果用戶完全重置系統(tǒng)((設置程序 -> 通用 -> 還原 -> 還原位置與隱私) ,這個廣告標示符會重新生成。另外如果用戶明確的還原廣告(設置程序-> 通用 -> 關于本機 -> 廣告 -> 還原廣告標示符) ,那么廣告標示符也會重新生成。
關于廣告標示符的還原,有一點需要注意:如果程序在后臺運行,此時用戶“還原廣告標示符”,然后再回到程序中,此時獲取廣 告標示符并不會立即獲得還原后的標示符。必須要終止程序,然后再重新啟動程序,才能獲得還原后的廣告標示符。
所以IDFA也不可以作為獲取唯一標識的方法,來識別用戶。
六.Vendor標示符 (IDFV-identifierForVendor)
Vendor標示符,是給Vendor標識用戶用的,每個設備在所屬同一個Vender的應用里,都有相同的值。其中的Vender是指應用提供商,但準確點說,是通過BundleID的反轉的前兩部分進行匹配,如果相同就是同一個Vender,例如對于com.taobao.app1, com.taobao.app2 這兩個BundleID來說,就屬于同一個Vender,共享同一個IDFV的值。和IDFA不同的是,IDFV的值是一定能取到的,所以非常適合于作為內部用戶行為分析的主id,來標識用戶,替代OpenUDID。
它是iOS 6中新增的,跟advertisingIdentifier一樣,該方法返回的是一個 NSUUID對象,可以獲得一個UUID。如果滿足條件“相同的一個程序里面-相同的vendor-相同的設備”,那么獲取到的這個屬性值就不會變。如果是“相同的程序-相同的設備-不同的vendor,或者是相同的程序-不同的設備-無論是否相同的vendor”這樣的情況,那么這個值是不會相同的。
NSString *strIDFV=[[[UIDevice currentDevice] identifierForVendor] UUIDString];
1
1
但是如果用戶將屬于此Vender的所有App卸載,則IDFV的值會被重置,即再重裝此Vender的App,IDFV的值和之前不同。
推送token+bundle_id的方法:
1、應用中增加推送用來獲取token
2、獲取應用bundle_id
3、根據(jù)token+bundle_id進行散列運算
apple push token保證設備唯一,但必須有網(wǎng)絡情況下才能工作,該方法并不是依賴于設備本身,而是依賴于apple push機制,所以當蘋果push做出改變時, 你獲取所謂的唯一標識也就隨之失效了。所以此方法還是不可取的。
八. NSUUID, CFUUID, IDFA, IDFV獲取的標識對比
首次運行
NSUUID:9D820D3A-4429-4918-97F7-A69588B388A4
CFUUID:80F961D0-1E6A-4ECD-A0A9-F58ED858FE20
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
二次運行
NSUUID:23AB8D3D-4F1D-45E2-8BD7-83B451125326
CFUUID:14DDBFCF-67A6-46B7-BB48-4EF2ADC5429F
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
卸載后, 重新安裝運行
NSUUID:BD934F9C-B7EC-4BD1-B65E-964C66537CAB
CFUUID:29654DE0-AC93-40F9-98AB-1E10A271AF8D
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
重啟后運行
NSUUID:82711557-3A17-4B82-8F18-09AADF9DD37B
CFUUID:FFBC73EC-CFBE-414C-870E-77C0714E0347
IDFA:687E6A90-50A3-4424-871C-BE255D050AFD
IDFV:8E740A99-283B-4F6A-87EF-443FB7778488
說了這么多, 才發(fā)現(xiàn)原來沒有一種方法是可行的。沒錯, 其實自從蘋果廢除UDID后, 就不能達到獲取設備真正的唯一標識了。因為這些方法中導致獲取的唯一標示產生改變的原因, 或是重新調用方法, 或是重啟設備, 或是卸載應用, 或是還原某些標識, 或者刷新系統(tǒng)…
所以, 不能達到從根本上獲取唯一標識, 我們只能做到盡可能接近。下面是我用過的方法。
我用的方法是將獲取的UUID永久存儲在設備的KeyChain中, 這個方法在應用第一次啟動時, 將獲取的UUID存儲進KeyChain中, 每次取的時候, 檢查本地鑰匙串中有沒有, 如果沒有則需要將獲取的UUID存儲進去。當你重啟設備, 卸載應用再次安裝,都不影響, 只是當設備刷機時, KeyChain會清空, 才會消失, 才會失效。
不只是這一種方法, 你也可以保存除UUID之外,其他合適的標識, 但利用KeyChain去存儲標識的方式應該是最接近的。
開發(fā)者可以在應用第一次啟動時調用一次,然后將該串存儲起來,以便以后替代UDID來使用。但是,如果用戶刪除該應用再次安裝時,又會生成新的字符串,所以不能保證唯一識別該設備。這就需要各路高手想出各種解決方案。所以,之前很多應用就采用MAC Address。但是現(xiàn)在如果用戶升級到iOS7(及其以后的蘋果系統(tǒng))后,他們機子的MAC Address就是一樣的,沒辦法做區(qū)分,只能棄用此方法,重新使用UUID來標識。如果使用UUID,就要考慮應用被刪除后再重新安裝時的處理。
一、在應用間利用KeyChain共享數(shù)據(jù)
我們可以把KeyChain理解為一個Dictionary,所有數(shù)據(jù)都以key-value的形式存儲,可以對這個Dictionary進行add、update、get、delete這四個操作。對于每一個應用來說,KeyChain都有兩個訪問區(qū),私有區(qū)和公共區(qū)。私有區(qū)是一個sandbox,本程序存儲的任何數(shù)據(jù)都對其他程序不可見。而要想在將存儲的內容放在公共區(qū),需要先聲明公共區(qū)的名稱,官方文檔管這個名稱叫“keychain access group”,聲明的方法是新建一個plist文件,名字隨便起,內容如下:
“yourAppID.com.yourCompany.whatever”就是你要起的公共區(qū)名稱,除了whatever字段可以隨便定之外,其他的都必須如實填寫。這個文件的路徑要配置在 Project->build setting->Code Signing Entitlements里,否則公共區(qū)無效,配置好后,須用你正式的證書簽名編譯才可通過,否則xcode會彈框告訴你code signing有問題。所以,蘋果限制了你只能同公司的產品共享KeyChain數(shù)據(jù),別的公司訪問不了你公司產品的KeyChain。
二、保存私密信息
iOS的keychain服務提供了一種安全的保存私密信息(密碼,序列號,證書等)的方式,每個ios程序都有一個獨立的keychain存儲。相對于NSUserDefaults、文件保存等一般方式,keychain保存更為安全,而且keychain里保存的信息不會因App被刪除而丟失,所以在重裝App后,keychain里的數(shù)據(jù)還能使用。
首先, 我先推薦兩篇文章,里面介紹了如利用keyChain和UUID永久獲得設備的唯一標識,Classes/KeychainItemWrapper.m和如何使用KeyChain保存和獲取UDID。
然后, 再介紹下我使用的方法以及封裝的工具類, 在應用里使用使用keyChain,我們需要導入Security.framework。下面介紹下, 我在其他庫基礎上封裝的一個獲取唯一標識的工具類:
#import#importNSString *constKEY_UDID_INSTEAD = @"com.myapp.udid.test";@interfaceLZKeychain : NSObject/**
本方法是得到 UUID 后存入系統(tǒng)中的 keychain 的方法
不用添加 plist 文件
程序刪除后重裝,仍可以得到相同的唯一標示
但是當系統(tǒng)升級或者刷機后,系統(tǒng)中的鑰匙串會被清空,此時本方法失效
*/+(NSString *)getDeviceIDInKeychain;@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import"LZKeychain.h"@implementationLZKeychain+(NSString*)getDeviceIDInKeychain{NSString*getUDIDInKeychain = (NSString*)[LZKeychain load:KEY_UDID_INSTEAD];NSLog(@"從keychain中獲取到的 UDID_INSTEAD %@",getUDIDInKeychain);if(!getUDIDInKeychain ||[getUDIDInKeychain isEqualToString:@""]||[getUDIDInKeychain isKindOfClass:[NSNull class]]) {? ? ? ? CFUUIDRef puuid = CFUUIDCreate(nil);? ? ? ? CFStringRef uuidString = CFUUIDCreateString(nil, puuid );NSString* result = (NSString*)CFBridgingRelease(CFStringCreateCopy(NULL, uuidString));? ? ? ? CFRelease(puuid);? ? ? ? CFRelease(uuidString);NSLog(@"\n \n \n _____重新存儲 UUID _____\n \n \n? %@",result);? ? ? ? [LZKeychain save:KEY_UDID_INSTEAD data:result];? ? ? ? getUDIDInKeychain = (NSString*)[LZKeychain load:KEY_UDID_INSTEAD];? ? }NSLog(@"最終 ———— UDID_INSTEAD %@",getUDIDInKeychain);returngetUDIDInKeychain;}#pragma mark - private+ (NSMutableDictionary*)getKeychainQuery:(NSString*)service {return[NSMutableDictionarydictionaryWithObjectsAndKeys:? ? ? ? ? ? (id)kSecClassGenericPassword,(id)kSecClass,? ? ? ? ? ? service, (id)kSecAttrService,? ? ? ? ? ? service, (id)kSecAttrAccount,? ? ? ? ? ? (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,nil];}+ (void)save:(NSString*)service data:(id)data {//Get search dictionaryNSMutableDictionary*keychainQuery = [selfgetKeychainQuery:service];//Delete old item before add new itemSecItemDelete((CFDictionaryRef)keychainQuery);//Add new object to search dictionary(Attention:the data format)[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];//Add item to keychain with the search dictionarySecItemAdd((CFDictionaryRef)keychainQuery,NULL);}+ (id)load:(NSString*)service {idret =nil;NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:service];//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[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{? ? ? ? ? ? ret = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)keyData];? ? ? ? }@catch(NSException*e) {NSLog(@"Unarchive of %@ failed: %@", service, e);? ? ? ? }@finally{? ? ? ? }? ? }if(keyData)? ? ? ? CFRelease(keyData);returnret;}+ (void)delete:(NSString*)service {NSMutableDictionary*keychainQuery = [selfgetKeychainQuery:service];? ? SecItemDelete((CFDictionaryRef)keychainQuery);}@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
參考資料: