天天看點

iOS擷取裝置的唯一辨別的方法總結以及最好的方法各種擷取裝置唯一辨別的方法介紹如何正确的擷取裝置的唯一辨別

各種擷取裝置唯一辨別的方法介紹

一.UDID(Unique Device Identifier)

UDID的全稱是Unique Device Identifier,它就是蘋果iOS裝置的唯一識别碼,它由40位16進制數的字母和數字組成(越獄的裝置通過某些工具可以改變裝置的UDID)。移動網絡可利用UDID來識别移動裝置,但是,從IOS5.0(2011年8月份)開始,蘋果宣布将不再支援用uniqueIdentifier方法擷取裝置的UDID,iOS5以下是可以用的。蘋果從iOS5開始就移除了通過代碼通路UDID的權限。從2013年5月1日起,試圖通路UIDIDs的程式将不再被稽核通過,替代的方案是開發者應該使用“在iOS 6中介紹的Vendor或Advertising标示符”。是以UDID是絕對是不能再使用了。

//UUID , 已廢除
NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];
           

為什麼蘋果反對開發人員使用UDID?

iOS 2.0版本以後UIDevice提供一個擷取裝置唯一辨別符的方法uniqueIdentifier,通過該方法我們可以擷取裝置的序列号,這個也是目前為止唯一可以确認唯一的标示符。 許多開發者把UDID跟使用者的真實姓名、密碼、住址、其它資料關聯起來;網絡窺探者會從多個應用收集這些資料,然後順藤摸瓜得到這個人的許多隐私資料。同時大部分應用确實在頻繁傳輸UDID和私人資訊。 為了避免集體訴訟,蘋果最終決定在iOS 5 的時候,将這一慣例廢除,開發者被引導生成一個唯一的辨別符,隻能檢測應用程式,其他的資訊不提供。現在應用試圖擷取UDID已被禁止且不允許上架。

二.UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的縮寫,中文意思是通用唯一識别碼。它是讓分布式系統中的所有元素,都能有唯一的辨識資訊,而不需要透過中央控制端來做辨識資訊的指定。這樣,每個人都可以建立不與其它人沖突的 UUID。在此情況下,就不需考慮資料庫建立時的名稱重複問題。蘋果公司建議使用UUID為應用生成唯一辨別字元串。

獲得的UUID值系統沒有存儲, 而且每次調用得到UUID,系統都會傳回一個新的唯一标示符。如果你希望存儲這個标示符,那麼需要自己将其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。

###1.CFUUID

從iOS2.0開始,CFUUID就已經出現了。它是CoreFoundatio包的一部分,是以API屬于C語言風格。CFUUIDCreate 方法用來建立CFUUIDRef,并且可以獲得一個相應的NSString,如下代碼:

CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
           

獲得的這個CFUUID值系統并沒有存儲。每次調用CFUUIDCreate,系統都會傳回一個新的唯一标示符。如果你希望存儲這個标示符,那麼需要自己将其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。

###2.NSUUID

NSUUID在iOS 6中才出現,這跟CFUUID幾乎完全一樣,隻不過它是Objective-C接口。+ (id)UUID 是一個類方法,調用該方法可以獲得一個UUID。通過下面的代碼可以獲得一個UUID字元串:

NSString *uuid = [[NSUUID UUID] UUIDString];
           

跟CFUUID一樣,這個值系統也不會存儲,每次調用的時候都會獲得一個新的唯一标示符。如果要存儲的話,你需要自己存儲。在我讀取NSUUID時,注意到擷取到的這個值跟CFUUID完全一樣(不過也可能不一樣)

三.open UDID

在iOS 5釋出時,uniqueIdentifier被棄用了,這引起了廣大開發者需要尋找一個可以替代UDID,并且不受蘋果控制的方案。由此OpenUDID成為了當時使用最廣泛的開源UDID替代方案。OpenUDID在工程中實作起來非常簡單,并且還支援一系列的廣告提供商。

OpenUDID利用了一個非常巧妙的方法在不同程式間存儲标示符 — 在粘貼闆中用了一個特殊的名稱來存儲标示符。通過這種方法,别的程式(同樣使用了OpenUDID)知道去什麼地方擷取已經生成的标示符(而不用再生成一個新的)。而且根據貢獻者的代碼和方法,和一些開發者的經驗,如果把使用了OpenUDID方案的應用全部都删除,再重新擷取OpenUDID,此時的OpenUDID就跟以前的不一樣。可見,這種方法還是不保險。

但是OpenUDID庫早已經棄用了, 在其官方的部落格中也指明了, 停止維護OpenUDID的原因是為了更好的向蘋果的舉措靠攏, 還指明了MAC Address不是一個好的選擇。

四.MAC Address

1.這個MAC位址是指什麼?有什麼用?

MAC(Medium/Media Access Control)位址,用來表示網際網路上每一個站點的辨別符,采用十六進制數表示,共六個位元組(48位)。其中,前三個位元組是由IEEE的注冊管理機構 RA負責給不同廠家配置設定的代碼(高位24位),也稱為“編制上唯一的辨別符” (Organizationally Unique Identifier),後三個位元組(低位24位)由各廠家自行指派給生産的擴充卡接口,稱為擴充辨別符(唯一性)。

MAC位址在網絡上用來區分裝置的唯一性,接入網絡的裝置都有一個MAC位址,他們肯定都是不同的,是唯一的。一部iPhone上可能有多個MAC位址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一個WIFI的,是以隻需擷取WIFI的MAC位址就好了,也就是en0的位址。

形象的說,MAC位址就如同我們身份證上的身份證号碼,具有全球唯一性。這樣就可以非常好的辨別裝置唯一性,類似與蘋果裝置的UDID号,通常的用途有:

1)用于一些統計與分析目的,利用使用者的操作習慣和資料更好的規劃産品;

2)作為使用者ID來唯一識别使用者,可以用遊客身份使用app又能在伺服器端儲存相應的資訊,省去使用者名、密碼等注冊過程。

2.如何使用Mac位址生成裝置的唯一辨別呢?

主要分三種:

1、直接使用“MAC Address”

2、使用“MD5(MAC Address)”

3、使用“MD5(Mac Address+bundle_id)”獲得“機器+應用”的唯一辨別(bundle_id 是應用的唯一辨別)

iOS7之前,因為Mac位址是唯一的, 一般app開發者會采取第3種方式來識别安裝對應app的裝置。為什麼會使用它?在iOS5之前,都是使用UDID的,後來被禁用。蘋果推薦使用UUID 但是也有諸多問題,進而使用MAC位址。而MAC位址跟UDID一樣,存在隐私問題,現在蘋果新釋出的iOS7上,如果請求Mac位址都會傳回一個固定值,那麼Mac Address+bundle_id這個值大家的裝置都變成一緻的啦,跟UDID一樣相當于被禁用, 是以Mac Address 是不能夠被使用為擷取裝置唯一辨別的。

五.廣告标示符(IDFA-identifierForIdentifier)

廣告标示符,在同一個裝置上的所有App都會取到相同的值,是蘋果專門給各廣告提供商用來追蹤使用者而設的。但好在Apple預設是允許追蹤的,而且一般使用者都不知道有這麼個設定,是以基本上用來監測推廣效果,是戳戳有餘了。

它是iOS 6中另外一個新的方法,提供了一個方法advertisingIdentifier,通過調用該方法會傳回一個NSUUID執行個體,最後可以獲得一個UUID,由系統存儲着的。

#import <AdSupport/AdSupport.h>
	NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
           

不過即使這是由系統存儲的,但是有幾種情況下,會重新生成廣告标示符。如果使用者完全重置系統((設定程式 -> 通用 -> 還原 -> 還原位置與隐私) ,這個廣告标示符會重新生成。另外如果使用者明确的還原廣告(設定程式-> 通用 -> 關于本機 -> 廣告 -> 還原廣告标示符) ,那麼廣告标示符也會重新生成。

關于廣告标示符的還原,有一點需要注意:如果程式在背景運作,此時使用者“還原廣告标示符”,然後再回到程式中,此時擷取廣 告标示符并不會立即獲得還原後的标示符。必須要終止程式,然後再重新啟動程式,才能獲得還原後的廣告标示符。

是以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];
           

但是如果使用者将屬于此Vender的所有App解除安裝,則IDFV的值會被重置,即再重裝此Vender的App,IDFV的值和之前不同。

七.推送token+bundle_id

推送token+bundle_id的方法:

1、應用中增加推送用來擷取token

2、擷取應用bundle_id

3、根據token+bundle_id進行散列運算

apple push token保證裝置唯一,但必須有網絡情況下才能工作,該方法并不是依賴于裝置本身,而是依賴于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

總結

說了這麼多, 才發現原來沒有一種方法是可行的。沒錯, 其實自從蘋果廢除UDID後, 就不能達到擷取裝置真正的唯一辨別了。因為這些方法中導緻擷取的唯一标示産生改變的原因, 或是重新調用方法, 或是重新開機裝置, 或是解除安裝應用, 或是還原某些辨別, 或者重新整理系統…

是以, 不能達到從根本上擷取唯一辨別, 我們隻能做到盡可能接近。下面是我用過的方法。

如何正确的擷取裝置的唯一辨別

我用的方法是将擷取的UUID永久存儲在裝置的KeyChain中, 這個方法在應用第一次啟動時, 将擷取的UUID存儲進KeyChain中, 每次取的時候, 檢查本地鑰匙串中有沒有, 如果沒有則需要将擷取的UUID存儲進去。當你重新開機裝置, 解除安裝應用再次安裝,都不影響, 隻是當裝置刷機時, KeyChain會清空, 才會消失, 才會失效。

不隻是這一種方法, 你也可以儲存除UUID之外,其他合适的辨別, 但利用KeyChain去存儲辨別的方式應該是最接近的。

利用keyChain和UUID永久獲得裝置的唯一辨別

開發者可以在應用第一次啟動時調用一次,然後将該串存儲起來,以便以後替代UDID來使用。但是,如果使用者删除該應用再次安裝時,又會生成新的字元串,是以不能保證唯一識别該裝置。這就需要各路高手想出各種解決方案。是以,之前很多應用就采用MAC Address。但是現在如果使用者更新到iOS7(及其以後的蘋果系統)後,他們機子的MAC Address就是一樣的,沒辦法做區分,隻能棄用此方法,重新使用UUID來辨別。如果使用UUID,就要考慮應用被删除後再重新安裝時的處理。

什麼是鑰匙串?

一、在應用間利用KeyChain共享資料

我們可以把KeyChain了解為一個Dictionary,所有資料都以key-value的形式存儲,可以對這個Dictionary進行add、update、get、delete這四個操作。對于每一個應用來說,KeyChain都有兩個通路區,私有區和公共區。私有區是一個sandbox,本程式存儲的任何資料都對其他程式不可見。而要想在将存儲的内容放在公共區,需要先聲明公共區的名稱,官方文檔管這個名稱叫“keychain access group”,聲明的方法是建立一個plist檔案,名字随便起,内容如下:

iOS擷取裝置的唯一辨別的方法總結以及最好的方法各種擷取裝置唯一辨別的方法介紹如何正确的擷取裝置的唯一辨別

“yourAppID.com.yourCompany.whatever”就是你要起的公共區名稱,除了whatever字段可以随便定之外,其他的都必須如實填寫。這個檔案的路徑要配置在 Project->build setting->Code Signing Entitlements裡,否則公共區無效,配置好後,須用你正式的證書簽名編譯才可通過,否則xcode會彈框告訴你code signing有問題。是以,蘋果限制了你隻能同公司的産品共享KeyChain資料,别的公司通路不了你公司産品的KeyChain。

二、儲存私密資訊

iOS的keychain服務提供了一種安全的儲存私密資訊(密碼,序列号,證書等)的方式,每個ios程式都有一個獨立的keychain存儲。相對于NSUserDefaults、檔案儲存等一般方式,keychain儲存更為安全,而且keychain裡儲存的資訊不會因App被删除而丢失,是以在重裝App後,keychain裡的資料還能使用。

實作代碼

首先, 我先推薦兩篇文章,裡面介紹了如利用keyChain和UUID永久獲得裝置的唯一辨別, Classes/KeychainItemWrapper.m和 如何使用KeyChain儲存和擷取UDID 。

然後, 再介紹下我使用的方法以及封裝的工具類, 在應用裡使用使用keyChain,我們需要導入Security.framework。下面介紹下, 我在其他庫基礎上封裝的一個擷取唯一辨別的工具類:

<LZKeychain.h>

#import <Foundation/Foundation.h>
#import <Security/Security.h>

NSString * const KEY_UDID_INSTEAD = @"com.myapp.udid.test";

@interface LZKeychain : NSObject

/**
 本方法是得到 UUID 後存入系統中的 keychain 的方法
 不用添加 plist 檔案
 程式删除後重裝,仍可以得到相同的唯一标示
 但是當系統更新或者刷機後,系統中的鑰匙串會被清空,此時本方法失效
 */
+(NSString *)getDeviceIDInKeychain;

@end
           

<LZKeychain.m>

#import "LZKeychain.h"

@implementation LZKeychain

+(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);
    return getUDIDInKeychain;
}

#pragma mark - private

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword,(id)kSecClass,
            service, (id)kSecAttrService,
            service, (id)kSecAttrAccount,
            (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
            nil];
}

+ (void)save:(NSString *)service data:(id)data {
    //Get search dictionary
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //Delete old item before add new item
    SecItemDelete((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 dictionary
    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery: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);
    return ret;
}

+ (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
           

參考資料:

The Developer’s Guide to Unique Identifiers

擷取裝置的唯一辨別