前言
获取设备唯一标识符,开发者大脑里产生的第一方案就是获取设备MAC地址,遗憾的是苹果iOS7之后获取到的mac是固定值,因此在通过MAC地址充当唯一标识此路不通;苹果iOS6另外一个新的方法(IDFA),提供了一个方法advertisingIdentifier,通过调用该方法会返回一个NSUUID实例,最后可以获得一个UUID,由系统存储着的,由于IDFA会出现取不到的情况,故绝不可以作为设备唯一标识符。还有IDFV(identifierForVendor),应用提供商标识符,如果用户将属于此Vender的所有App卸载,则IDFV的值会被重置,即再重装此Vender的App,IDFV的值和之前不同,因此想通过IDFV作为设备唯一标识符,需要KeyChain配合(KeyChain的优缺点)本文不阐述。
1、结构设计方案(KeyChain+IDFV)
(1)创建一个共用的KeyChain(钥匙串)的管理对象,负责对KeyChain中Item的增删改查以及Item是否App之间共享等等;
(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
2、保存与获取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
总结
Keychain内部的数据会自动加密。如果设备没有越狱并且不暴力破解,keychain确实很安全。但是越狱后的设备,keychain就很危险了,因此还需要对重要信息进行加密处理再放入Keychain。