Keychain 介紹
Keychain Services 是 OS X 和 iOS 都提供一種安全地存儲(chǔ)敏感信息的工具,比如,存儲(chǔ)用戶ID,密碼,和證書等。存儲(chǔ)這些信息可以免除用戶重復(fù)輸入用戶名和密碼的過(guò)程。Keychain Services 的安全機(jī)制保證了存儲(chǔ)這些敏感信息不會(huì)被竊取。簡(jiǎn)單說(shuō)來(lái),Keychain 就是一個(gè)安全容器。
Keychain 的結(jié)構(gòu)
Keychain 可以包含任意數(shù)量的 keychain item。每一個(gè) keychain item 包含數(shù)據(jù)和一組屬性。對(duì)于一個(gè)需要保護(hù)的 keychain item,比如密碼或者私鑰(用于加密或者解密的string字節(jié))數(shù)據(jù)是加密的,會(huì)被 keychain 保護(hù)起來(lái)的;對(duì)于無(wú)需保護(hù)的 keychain item,例如,證書,數(shù)據(jù)未被加密。
跟keychain item有關(guān)系的取決于item的類型;應(yīng)用程序中最常用的是網(wǎng)絡(luò)密碼(Internet passwrods)和普通的密碼。正如你所想的,網(wǎng)絡(luò)密碼像安全域(security domain)、協(xié)議、和路徑等一些屬性。在OSX中,當(dāng)keychain被鎖的時(shí)候加密的item沒(méi)辦法訪問(wèn),如果你想要該問(wèn)被鎖的item,就會(huì)彈出一個(gè)對(duì)話框,需要你輸入對(duì)應(yīng)keychain的密碼。當(dāng)然,未有密碼的keychain你可以隨時(shí)訪問(wèn)。但在iOS中,你只可以訪問(wèn)你自已的keychain items;
item可以指定為以下的類型:
extern CFTypeRef kSecClassGenericPassword
extern CFTypeRef kSecClassInternetPassword
extern CFTypeRef kSecClassCertificate
extern CFTypeRef kSecClassKey
extern CFTypeRef kSecClassIdentity OSX_AVAILABLE_STARTING(MAC_10_7, __IPHONE_2_0);
Keychain的特點(diǎn)
- 數(shù)據(jù)并不存放在App的Sanbox中,即使刪除了App,資料依然保存在keychain中。如果重新安裝了app,還可以從keychain獲取數(shù)據(jù)。
- keychain的數(shù)據(jù)可以用過(guò)group方式,讓程序可以在App間共享。不過(guò)得要相同TeamID
- keychain的數(shù)據(jù)是經(jīng)過(guò)加密的
Keychain的使用
大多數(shù)iOS應(yīng)用需要用到Keychain, 都用來(lái)添加一個(gè)密碼,修改一個(gè)已存在Keychain item或者取回密碼。Keychain提供了以下的操作
- SecItemAdd 添加一個(gè)item
- SecItemUpdate 更新已存在的item
- SecItemCopyMatching 搜索一個(gè)已存在的item
- SecItemDelete 刪除一個(gè)keychain item
Talk is Cheap,Show you the Code
根據(jù)特定的Service創(chuàng)建一個(gè)用于操作KeyChain的Dictionary
+ (NSMutableDictionary *)keyChainQueryDictionaryWithService:(NSString *)service{
NSMutableDictionary *keyChainQueryDictaionary = [[NSMutableDictionary alloc]init];
[keyChainQueryDictaionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrService];
[keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrAccount];
return keyChainQueryDictaionary;
}
添加數(shù)據(jù)
+ (BOOL)addData:(id)data forService:(NSString *)service{
NSMutableDictionary *keychainQuery = [self keyChainQueryDictionaryWithService:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
OSStatus status= SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
if (status == noErr) {
return YES;
}
return NO;
}
搜索數(shù)據(jù)
+ (id)queryDataWithService:(NSString *)service {
id result;
NSMutableDictionary *keyChainQuery = [self keyChainQueryDictionaryWithService:service];
[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 {
result = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
}
@catch (NSException *exception) {
NSLog(@"不存在數(shù)據(jù)");
}
@finally {
}
}
if (keyData) {
CFRelease(keyData);
}
return result;
}
更新數(shù)據(jù)
+ (BOOL)updateData:(id)data forService:(NSString *)service{
NSMutableDictionary *searchDictionary = [self keyChainQueryDictionaryWithService:service];
if (!searchDictionary) {
return NO;
}
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
[updateDictionary setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
(CFDictionaryRef)updateDictionary);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
刪除數(shù)據(jù)
+ (BOOL)deleteDataWithService:(NSString *)service{
NSMutableDictionary *keyChainDictionary = [self keyChainQueryDictionaryWithService:service];
OSStatus status = SecItemDelete((CFDictionaryRef)keyChainDictionary);
if (status == noErr) {
return YES;
}
return NO;
}
Keychain 共享數(shù)據(jù)
先開啟Keychain share,選中項(xiàng)目的Target -> Capabilities -> Keychain Groups。打開這個(gè)選項(xiàng)。

同時(shí)在你的項(xiàng)目會(huì)生成一個(gè)entitlements文件。里面會(huì)有Access group,值應(yīng)該是
$(AppIdentifierPrefix)cn.xxx.KeyChainLearn
AppIdentifierPrefix表示發(fā)布者的一個(gè)身份,這個(gè)可以在蘋果開發(fā)者后臺(tái)可以找得到。
可以從在Info.plist文件中新增一組key-value
Key: AppIdentifierPrefix
Value: $(AppIdentifierPrefix)
最后創(chuàng)建Keychain Item的時(shí)候,需要指定的相應(yīng)的group
NSString *perfix = [[[NSBundle mainBundle]infoDictionary]objectForKey:@"AppIdentifierPrefix"];
NSString *groupString = [NSString stringWithFormat:@"%@cn.xiaozhi.KeyChainLearn",perfix];
[keyChainQueryDictaionary setObject:groupString forKey:(id)kSecAttrAccessGroup];
在其他需要共享數(shù)據(jù)的應(yīng)用也是重復(fù)以上的操作,不過(guò)得保證AppIdentifierPrefix相同。
總結(jié)
在保存一些重要的信息的時(shí)候,我們可以使用Keychain,但不是絕對(duì)安全的,可以把數(shù)據(jù)加密后再放在Keychain。Keychain還可以通過(guò)iCloud備份跟跨設(shè)備共享。
參考文檔: