iOS 开发 - 设备唯一标志符 - keychain 使用与多个APP之间共享keychain数据的使用

这篇攻略是利用keychain来持久化存储数据,所以可以利用这个来做设备唯一标志符。另外你只要按照我的步骤操作就没有什么问题,简称傻瓜式操作~~

1.先来了解一下keychain


这里我想很多人都对yourAppID理解不对,这个yourAppID不是工程的appid,而是在开发者中心的个人界面就有;其次yourAppID只对应所使用的开发者账号。


2.对这个东西有所理解后,开始动手操作了。


把这两个类文件添加到你项目里,按照我的文件命名即可。


第一个 :

#import@interface KeychainItemWrapper : NSObject

{

NSMutableDictionary *keychainItemData;        // The actual keychain item data backing store.

NSMutableDictionary *genericPasswordQuery;    // A placeholder for the generic keychain item query used to locate the item.

}

@property (nonatomic, retain) NSMutableDictionary *keychainItemData;

@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery;

// Designated initializer.

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;

- (void)setObject:(id)inObject forKey:(id)key;

- (id)objectForKey:(id)key;

// Initializes and resets the default generic keychain item data.

- (void)resetKeychainItem;

@end


#import "KeychainItemWrapper.h"#import/*

These are the default constants and their respective types,

available for the kSecClassGenericPassword Keychain Item class:

kSecAttrAccessGroup            -        CFStringRef

kSecAttrCreationDate        -        CFDateRef

kSecAttrModificationDate    -        CFDateRef

kSecAttrDescription            -        CFStringRef

kSecAttrComment                -        CFStringRef

kSecAttrCreator                -        CFNumberRef

kSecAttrType                -        CFNumberRef

kSecAttrLabel                -        CFStringRef

kSecAttrIsInvisible            -        CFBooleanRef

kSecAttrIsNegative            -        CFBooleanRef

kSecAttrAccount                -        CFStringRef

kSecAttrService                -        CFStringRef

kSecAttrGeneric                -        CFDataRef

See the header file Security/SecItem.h for more details.

*/

@interface KeychainItemWrapper (PrivateMethods)

/*

The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was

to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the

Keychain API expects as a validly constructed container class.

*/

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;

// Updates the item in the keychain, or adds it if it doesn't exist.

- (void)writeToKeychain;

@end

@implementation KeychainItemWrapper

@synthesize keychainItemData, genericPasswordQuery;

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

{

if (self = [super init])

{

NSAssert(account != nil || service != nil, @"Both account and service are nil.  Must specifiy at least one.");

// Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and

// kSecAttrService are used as unique identifiers differentiating keychain items from one another

genericPasswordQuery = [[NSMutableDictionary alloc] init];

[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

[genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];

[genericPasswordQuery setObject:service forKey:(id)kSecAttrService];

// The keychain access group attribute determines if this item can be shared

// amongst multiple apps whose code signing entitlements contain the same keychain access group.

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

// Use the proper search constants, return only the attributes of the first match.

[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];

[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

NSMutableDictionary *outDictionary = nil;

if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)

{

// Stick these default values into keychain item if nothing found.

[self resetKeychainItem];

//Adding the account and service identifiers to the keychain

[keychainItemData setObject:account forKey:(id)kSecAttrAccount];

[keychainItemData setObject:service forKey:(id)kSecAttrService];

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

}

else

{

// load the saved data from Keychain.

self.keychainItemData = [self secItemFormatToDictionary:outDictionary];

}

[outDictionary release];

}

return self;

}

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;

{

if (self = [super init])

{

// Begin Keychain search setup. The genericPasswordQuery leverages the special user

// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain

// items which may be included by the same application.

genericPasswordQuery = [[NSMutableDictionary alloc] init];

[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];

// The keychain access group attribute determines if this item can be shared

// amongst multiple apps whose code signing entitlements contain the same keychain access group.

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

// Use the proper search constants, return only the attributes of the first match.

[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];

[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

NSMutableDictionary *outDictionary = nil;

if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)

{

// Stick these default values into keychain item if nothing found.

[self resetKeychainItem];

// Add the generic attribute and the keychain access group.

[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

}

else

{

// load the saved data from Keychain.

self.keychainItemData = [self secItemFormatToDictionary:outDictionary];

}

[outDictionary release];

}

return self;

}

- (void)dealloc

{

[keychainItemData release];

[genericPasswordQuery release];

[super dealloc];

}

- (void)setObject:(id)inObject forKey:(id)key

{

if (inObject == nil) return;

id currentObject = [keychainItemData objectForKey:key];

if (![currentObject isEqual:inObject])

{

[keychainItemData setObject:inObject forKey:key];

[self writeToKeychain];

}

}

- (id)objectForKey:(id)key

{

return [keychainItemData objectForKey:key];

}

- (void)resetKeychainItem

{

OSStatus junk = noErr;

if (!keychainItemData)

{

self.keychainItemData = [[NSMutableDictionary alloc] init];

}

else if (keychainItemData)

{

NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];

junk = SecItemDelete((CFDictionaryRef)tempDictionary);

NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );

}

// Default attributes for keychain item.

[keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];

[keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];

[keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];

// Default data for keychain item.

[keychainItemData setObject:@"" forKey:(id)kSecValueData];

}

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert

{

// The assumption is that this method will be called with a properly populated dictionary

// containing all the right key/value pairs for a SecItem.

// Create a dictionary to return populated with the attributes and data.

NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];

// Add the Generic Password keychain item class attribute.

[returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

// Convert the NSString to NSData to meet the requirements for the value type kSecValueData.

// This is where to store sensitive data that should be encrypted.

NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];

[returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];

return returnDictionary;

}

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert

{

// The assumption is that this method will be called with a properly populated dictionary

// containing all the right key/value pairs for the UI element.

// Create a dictionary to return populated with the attributes and data.

NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];

// Add the proper search key and class attribute.

[returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

[returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

// Acquire the password data from the attributes.

NSData *passwordData = NULL;

if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)

{

// Remove the search, class, and identifier key/value, we don't need them anymore.

[returnDictionary removeObjectForKey:(id)kSecReturnData];

// Add the password to the dictionary, converting from NSData to NSString.

NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]

encoding:NSUTF8StringEncoding] autorelease];

[returnDictionary setObject:password forKey:(id)kSecValueData];

}

else

{

// Don't do anything if nothing is found.

NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");

}

[passwordData release];

return returnDictionary;

}

- (void)writeToKeychain

{

NSDictionary *attributes = NULL;

NSMutableDictionary *updateItem = NULL;

OSStatus result;

if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)

{

// First we need the attributes from the Keychain.

updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];

// Second we need to add the appropriate search key/values.

[updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];

// Lastly, we need to set up the updated attribute list being careful to remove the class.

NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];

[tempCheck removeObjectForKey:(id)kSecClass];

#if TARGET_IPHONE_SIMULATOR

// Remove the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

//

// The access group attribute will be included in items returned by SecItemCopyMatching,

// which is why we need to remove it before updating the item.

[tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];

#endif

// An implicit assumption is that you can only update a single item at a time.

result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);

NSAssert( result == noErr, @"Couldn't update the Keychain Item." );

}

else

{

// No previous item found; add the new one.

result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);

NSAssert( result == noErr, @"Couldn't add the Keychain Item." );

}

}

@end

```


第二个:

```

#import@interface AppUntils : NSObject

+(void)saveUUIDToKeyChain;

+(NSString *)readUUIDFromKeyChain;

+ (NSString *)getUUIDString;

@end


#import "AppUntils.h"#import#import "KeychainItemWrapper.h"

@implementation AppUntils

#pragma mark - 保存和读取UUID

+(void)saveUUIDToKeyChain{

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];

NSString *string = [keychainItem objectForKey: (__bridge id)kSecAttrGeneric];

if([string isEqualToString:@""] || !string){

[keychainItem setObject:[self getUUIDString] forKey:(__bridge id)kSecAttrGeneric];

}

}

+(NSString *)readUUIDFromKeyChain{

KeychainItemWrapper *keychainItemm = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];

NSString *UUID = [keychainItemm objectForKey: (__bridge id)kSecAttrGeneric];

return UUID;

}

+ (NSString *)getUUIDString

{

CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);

CFStringRef strRef = CFUUIDCreateString(kCFAllocatorDefault , uuidRef);

NSString *uuidString = [(__bridge NSString*)strRef stringByReplacingOccurrencesOfString:@"-" withString:@""];

CFRelease(strRef);

CFRelease(uuidRef);

return uuidString;

}

@end




3.因为KeychainItemWrapper.m文件是在非ARC环境下运行的,所以需要设置非arc编译环境,在tag下设置,如图所示:

4.在项目相同的目录下创建KeychainAccessGroups.plist文件,不是在info.plist里。

按照图里要求填写,前缀是appid,我自己瞎写的一个,你需要用对应你的开发者账号的来弄,还有公司名。


5.大功告成咯~~我们就随便用用看吧~~


添加两个头文件,这个方法就能获取到UUID

要取出来,用  readUUIDFromKeyChain  即可。


最后

有时候我们会发现有些app比较赖皮,卸载后再装竟然不需要登录就能上去,这个大多数原因也是因为他们把用户数据保存在了 keychain 里;另外如果涉及到安全性较高的,即使有两款产品,我们也不会建议让他们使用同一个 keychain。当然,如果只是采集一些用户行为数据的话,这个还是可以的



参考文章的链接 :

获取iOS设备唯一标示UUID

iOS 开发keychain 使用与多个APP之间共享keychain数据的使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容