獲取iOS設備唯一標識(UUID)并保存到鑰匙串

前言:

1 .為了統計和檢測應用的使用數據,幾乎每家公司都有獲取唯一標識的業務需求,在iOS5以前獲取唯一標識,可以獲取到系統提供的方法UDID(Unique Device Identifier),后來被出于用戶隱私的考慮被Apple官方禁止掉了。于是,大家開始在iOS6中使用 MAC 地址(Medium/Media Access Control) ,后來又被Apple官方在iOS7中禁止掉了。蘋果及其國外的IT公司都會比較注重用戶隱私,所以今后一但有比較靠譜的獲取唯一標示的方法放出,蘋果肯定會封堵。

2.**  ** 在非越獄的手機上獲取某個硬件信息生成唯一標識,第一只能找到蘋果的漏洞,第二就是調用一些私有接口,顯然這兩條路都比較艱難,并不可持續發展,所以網上大部分的唯一標識都是從操作系統層面獲取的,在重置手機系統的時候都會被清除,在系統升級、卸載重裝、備份恢復都可以保留,現在本人尚未發現可以使用嚴格意義上的唯一標識。接下來我想跟大家探討的是如何通過“合法”的手段來盡量拿到不會輕易發生變化的“唯一標識”。

3.**  **在2013年3月21日蘋果已經通知開發者,從2013年5月1日起,訪問UIDID的應用將不再能通過審核,替代的方案是開發者應該使用“在iOS 6中介紹的Vendor或Advertising標示符”。

4.MAC地址不能再用來設別設備
**  **還有一個生成iOS設備唯一標示符的方法是使用iOS設備的Media Access Control(MAC)地址。一個MAC地址是一個唯一的號碼,它是物理網絡層級方面分配給網絡適配器的。這個地址蘋果還有其他的名字,比如說是硬件地址(Hardware Address)或是Wifi地址,都是指同樣的東西。
  有很多工程和框架都使用這個方法來生成唯一的設備ID。比如說ODIN。然而,蘋果并不希望有人通過MAC地址來分辨用戶,所以如果你在iOS7系統上查詢MAC地址,它現在只會返回02:00:00:00:00:00。

5 **  ** 講真蘋果這傲嬌的小脾氣什么時候能能改改,不過這樣 對于用戶隱私的保護,確實起到很大作用,而且蘋果也沒有把路堵死,現在蘋果明確的表明你應該使用-[UIDevice identifierForVendor]或是-[ASIdentifierManager advertisingIdentifier]來作為你框架和應用的唯一標示符。坦白的來說,應對這些變化也不是那么的難,見以下代碼片段:

NSString *identifierForVendor = [[UIDevice currentDevice].identifierForVendor UUIDString]; 
NSString *identifierForAdvertising = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString]; 

但是這樣用法有個缺點,就是程序每次被刪除以后,獲取到的都是新的uuid,并不能實現每個手機的唯一性.
所以下面要講的就是這個問題的解決辦法.

**  **對于這個問題有一個可行的辦法,就是把獲取到的uuid保存在鑰匙串里面,這樣即使程序刪除重裝,獲取到的uuid一直是同一個,實現了手機的唯一標識的作用.

保存鑰匙串 我們需要用到keychain,除此之外,Code Signing Entitlements的創建方法也不夠嚴謹。下面教大家一種方法,簡單快速.

1.新建一個工程,看一下自己的Bundle Id.這個Bundle Id 要和你用真機測試時的證書上面的Bundle Id相匹配。

注意這個Bundle identifier

2.Target - Capabilities - Keychain Sharing - ON

注意圓圈里面的保持一致

這步主要目的是打開Keychain Sharing,將它由灰色狀態的OFF改為藍色狀態的ON。

會自動生成這個

左側的目錄會自動生成Entitlements文件,不需要自己創建了。

也就是說,Bundle Identifier、Keychain Sharing的Keychain Groups、Entitlements文件的Keychain Access Groups的第一個元素,它們要保持上圖所示的一致性。
設置好了以后可以運行下程序,沒問題可以進行下一步。

.傳說中的uuid類和keychain類來啦
既然蘋果的keychain方法會崩潰而且有些復雜,我們只保存一個uuid的話可以用下面的簡單方法:


KeyChainStore.h

#import <Foundation/Foundation.h>

@interface KeyChainStore : NSObject

+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)deleteKeyData:(NSString *)service;

@end



KeyChainStore.m

//
//  KeyChainStore.m
//  getUUID
//
//  Created by ckl@pmm on 16/9/18.
//  Copyright ? 2016年 CKLPronetway. All rights reserved.
//

#import "KeyChainStore.h"

@implementation KeyChainStore

+ (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:(__bridge NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    if (keyData)
        CFRelease(keyData);
    return ret;
}

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

#import <Foundation/Foundation.h>

@interface getUUID : NSObject
+(NSString *)getUUID;
@end
getUUID.m

//
//  getUUID.m
//  getUUID
//
//  Created by ckl@pmm on 16/9/18.
//  Copyright ? 2016年 CKLPronetway. All rights reserved.
//

#import "getUUID.h"
#import "KeyChainStore.h"
#define  KEY_USERNAME_PASSWORD @"com.company.app.usernamepassword"
#define  KEY_USERNAME @"com.company.app.username"
#define  KEY_PASSWORD @"com.company.app.password"

@implementation getUUID

+(NSString *)getUUID
{
    NSString * strUUID = (NSString *)[KeyChainStore load:@"com.company.app.usernamepassword"];
    
    //首次執行該方法時,uuid為空
    if ([strUUID isEqualToString:@""] || !strUUID)
    {
        //生成一個uuid的方法
        CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
        
        strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef));
        
        //將該uuid保存到keychain
        [KeyChainStore save:KEY_USERNAME_PASSWORD data:strUUID];
        
    }
    return strUUID;
}

@end

在Viewcontroller里面執行如下代碼

#import "ViewController.h"
#import "getUUID.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@" uuid  is  ---> %@",[getUUID getUUID]);
    
    // Do any additional setup after loading the view, typically from a nib.
}

打印出來類似于以下的長串字符:

獲取到的uuid.png

把程序卸載掉然后重新運行一次,獲取到的還是上次保存的uuid.
不知道手機越獄以后,會不會改變,因為樓主手機版本是最新的9.3.5,身邊還沒相關越獄設備,希望大家可以自行測試一下.并告知樓主.聯系方式 : QQ :576484150

參考地址:
:http://blog.sina.com.cn/s/blog_5971cdd00102vqgy.html
:http://blog.csdn.net/chy555chy/article/details/51628079

自己寫了一個demo,已經放在gitHub上了,有興趣的同學可以下載下來瞅瞅.
demo 地址 :https://github.com/chengkunlun/getUUID

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容