前言安全
獲取設備惟一標識符,開發者大腦裏產生的第一方案就是獲取設備MAC地址,遺憾的是蘋果iOS7以後獲取到的mac是固定值,所以在經過MAC地址充當惟一標識此路不通;蘋果iOS6另一個新的方法(IDFA),提供了一個方法advertisingIdentifier,經過調用該方法會返回一個NSUUID實例,最後能夠得到一個UUID,由系統存儲着的,因爲IDFA會出現取不到的狀況,故毫不能夠做爲設備惟一標識符。還有IDFV(identifierForVendor),應用提供商標識符,若是用戶將屬於此Vender的全部App卸載,則IDFV的值會被重置,即再重裝此Vender的App,IDFV的值和以前不一樣,所以想經過IDFV做爲設備惟一標識符,須要KeyChain配合(KeyChain的優缺點)本文不闡述。ide
一、結構設計方案(KeyChain+IDFV)ui
(1)建立一個共用的KeyChain(鑰匙串)的管理對象,負責對KeyChain中Item的增刪改查以及Item是否App之間共享等等;atom
(2)而後根據需求二次封裝,無論你要保存UUID仍是帳戶密碼,你須要作的就是建立特定的廣利對象(好比說下文的XPQKeychainUUID用於管理UUID)封裝相應的存儲邏輯,最後經過XPQKeychainManager寫入KeyChain。加密
#import <Foundation/Foundation.h> @interface XPQKeychainManager : NSObject - (instancetype)initSecAttrAccessGroup:(NSString *)secAttrAccessGroup; - (id)secValueDataForService:(NSString *)aService; - (id)secValueDataForService:(NSString *)aService account:(NSString *)account; - (BOOL)saveKeychainData:(id)data service:(NSString *)aService; - (BOOL)saveKeychainData:(id)data service:(NSString *)aService account:(NSString *)account; - (BOOL)deleteKeychainDataForService:(NSString *)aService; - (BOOL)deleteKeychainDataForService:(NSString *)aService account:(NSString *)account; @end #import "XPQKeychainManager.h" @interface XPQKeychainManager () @property (nonatomic, copy) NSString *secAttrAccessGroup; @end @implementation XPQKeychainManager - (instancetype)init { return [self initSecAttrAccessGroup:nil]; } - (instancetype)initSecAttrAccessGroup:(NSString *)secAttrAccessGroup { self = [super init]; if (self) { self.secAttrAccessGroup = secAttrAccessGroup; } return self; } - (NSMutableDictionary *)queryKeychainForService:(NSString *)aService account:(NSString *)account { return [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass, aService, (__bridge_transfer id)kSecAttrService, account, (__bridge_transfer id)kSecAttrAccount, (__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible, nil]; } - (id)secValueDataForService:(NSString *)aService { return [self secValueDataForService:aService account:aService]; } - (id)secValueDataForService:(NSString *)aService account:(NSString *)account { if (!aService && !account) { return nil; } NSMutableDictionary *keychainQuery = [self queryKeychainForService:aService account:account]; if (self.secAttrAccessGroup) { [keychainQuery setObject:self.secAttrAccessGroup forKey:(__bridge_transfer id)kSecAttrAccessGroup]; } NSMutableDictionary *attributeQuery = [keychainQuery mutableCopy]; [attributeQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnAttributes]; CFTypeRef attrResult = NULL; OSStatus status = SecItemCopyMatching(((__bridge_retained CFDictionaryRef)attributeQuery), (CFTypeRef *)&attrResult); if (status != noErr) { return nil; } NSMutableDictionary *secValueDataQuery = [keychainQuery mutableCopy]; [secValueDataQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData]; CFTypeRef resData = NULL; status = SecItemCopyMatching(((__bridge_retained CFDictionaryRef)secValueDataQuery), (CFTypeRef *)&resData); NSData *resultData = (__bridge_transfer NSData *)resData; if (status != noErr) { return nil; } return resultData ? [NSKeyedUnarchiver unarchiveObjectWithData:resultData] : nil; } - (BOOL)saveKeychainData:(id)data service:(NSString *)aService { return [self saveKeychainData:data service:aService account:aService]; } - (BOOL)saveKeychainData:(id)data service:(NSString *)aService account:(NSString *)account { if (!aService && !account) { return NO; } NSMutableDictionary *keychainQuery = [self queryKeychainForService:aService account:account]; if (self.secAttrAccessGroup) { [keychainQuery setObject:self.secAttrAccessGroup forKey:(__bridge_transfer id)kSecAttrAccessGroup]; } id keychainData = [self secValueDataForService:aService account:account]; OSStatus status = noErr; if (keychainData) { NSMutableDictionary *updateKeychainData = [keychainQuery mutableCopy]; if (![keychainData isEqualToString:data]) { NSMutableDictionary *tempCheck = [NSMutableDictionary dictionaryWithObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData]; status = SecItemUpdate(((__bridge_retained CFDictionaryRef)updateKeychainData), (__bridge_retained CFDictionaryRef)tempCheck); } } else { NSMutableDictionary *addKeychainData = [keychainQuery mutableCopy]; [addKeychainData setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData]; status = SecItemAdd(((__bridge_retained CFDictionaryRef)addKeychainData), NULL); } if (status != noErr) { return NO; } return YES; } - (BOOL)deleteKeychainDataForService:(NSString *)aService { return [self deleteKeychainDataForService:aService account:aService]; } - (BOOL)deleteKeychainDataForService:(NSString *)aService account:(NSString *)account { if (!aService && !account) { return NO; } NSMutableDictionary *keychainQuery = [self queryKeychainForService:aService account:account]; OSStatus status = SecItemDelete(((__bridge_retained CFDictionaryRef)keychainQuery)); if (status != noErr) { return NO; } return YES; } @end
二、保存與獲取IDFV,關鍵代碼NSString *uuid =
[[UIDevice currentDevice].identifierForVendor UUIDString];設計
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface XPQKeychainUUID : NSObject - (id)readUUID; - (BOOL)deleteItem; @end #import "XPQKeychainUUID.h" #import "XPQKeychainManager.h" @interface XPQKeychainUUID () @property (nonatomic, copy) NSString *uuid; @property (nonatomic, strong) XPQKeychainManager *keychainManager; @end static NSString * const KEY_IN_KEYCHAIN_UUID = @"com.xpq.keychain.uuid"; @implementation XPQKeychainUUID - (instancetype)init { self = [super init]; if (self) { self.keychainManager = [[XPQKeychainManager alloc] init]; } return self; } - (BOOL)save:(id)data service:(NSString *)service { return [self.keychainManager saveKeychainData:data service:KEY_IN_KEYCHAIN_UUID]; } - (NSString *)getUUID { return [[UIDevice currentDevice].identifierForVendor UUIDString]; } - (id)readUUID { if (!_uuid || _uuid.length == 0) { NSString *strUUID = [self load:KEY_IN_KEYCHAIN_UUID]; if (!strUUID || strUUID.length == 0) { strUUID = [self getUUID]; [self save:strUUID]; } _uuid = strUUID; } return _uuid; } - (id)load:(NSString *)service { return [self.keychainManager secValueDataForService:KEY_IN_KEYCHAIN_UUID]; } - (BOOL)save:(id)data { return [self save:data service:KEY_IN_KEYCHAIN_UUID]; } - (BOOL)deleteItem { return [self.keychainManager deleteKeychainDataForService:KEY_IN_KEYCHAIN_UUID]; } @end
總結code
Keychain內部的數據會自動加密。若是設備沒有越獄而且不暴力破解,keychain確實很安全。可是越獄後的設備,keychain就很危險了,所以還須要對重要信息進行加密處理再放入Keychain。對象