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

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

1.先来了解一下keychain


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

这里我想很多人都对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下设置,如图所示:

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

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

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


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


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

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

要取出来,用  readUUIDFromKeyChain  即可。


最后

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



参考文章的链接 :

获取iOS设备唯一标示UUID

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

你可能感兴趣的:(iOS 开发 - 设备唯一标志符 - keychain 使用与多个APP之间共享keychain数据的使用)