keychain使用说明书
2017-09-05 » iOS开发基础1、什么是keychain
根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个在所有app之外的sqlite数据库。
如果我们手动把自己的私密信息加密,然后通过写文件保存在本地,再从本地取出不仅麻烦,而且私密信息也会随着App的删除而丢失。iOS的Keychain能完美的解决这些问题。并且从iOS 3.0开始,Keychain还支持跨程序分享。这样就极大的方便了用户。省去了很多要记忆密码的烦恼。
Keychain内部可以保存很多的信息。每条信息作为一个单独的keychain item,keychain item一般为一个字典,每条keychain item包含一条data和很多attributes。举个例子,一个用户账户就是一条item,用户名可以作为一个attribute , 密码就是data。 keychain虽然是可以保存15000条item,每条50个attributes,但是苹果工程师建议最好别放那么多,存几千条密码,几千字节没什么问题。
如果把keychain item的类型指定为需要保护的类型比如password或者private key,item的data会被加密并且保护起来,如果把类型指定为不需要保护的类型,比如certificates,item的data就不会被加密。
keychain优点:
1.每个组(keychain-access-groups)之间数据访问隔离,没有权限的app不能读取他人数据,保证了数据安全。
2.全局统一存储,即使删除了app,keychain里的数据也还在,下次重新安装app后依然能访问
3.存储后的数据加密
4.相同的 Team ID App 可以共享keychain中的数据
keychain缺点:
1.删除app后不会自动清除keychain里的数据,如果存储密码等敏感数据会有一定风险(越狱后keychain能被导出来)
2、keychain的基本用法
Keychain 的操作:
- SecItemCopyMatching 查
- SecItemAdd 增
- SecItemUpdate 改
- SecItemDelete 删
//通过Dictionary去查询、修改、新增
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
//指定item的类型为GenericPassword
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
//类型为GenericPassword的信息必须提供以下两条属性作为unique identifier
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrService];
return searchDictionary;
}
//查询
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
//在搜索keychain item的时候必须提供下面的两条用于搜索的属性
//只返回搜索到的第一条item
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
//返回item的kSecValueData
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
NSData *result = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
(CFTypeRef *)&result);
[searchDictionary release];
return result;
}
//新增
//修改
类型为GenericPassword的item必须使用kSecAttrAccount和kSecAttrService作为主要的key,但是这个类仅仅以kSecAttrGeneric作主要的key。所以在用它添加item的时候容易出现重复添加的错误。
每种类型的Keychain item都有不同的键作为主要的Key也就是唯一标示符用于搜索,更新和删除,Keychain内部不允许添加重复的Item。
keychain item的类型,也就是kSecClass键的值 | 主要的Key |
---|---|
kSecClassGenericPassword | kSecAttrAccount,kSecAttrService |
kSecClassInternetPassword | kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol,kSecAttrAuthenticationType, kSecAttrPortkSecAttrPath |
kSecClassCertificate | kSecAttrCertificateType, kSecAttrIssuerkSecAttrSerialNumber |
kSecClassKey | kSecAttrApplicationLabel, kSecAttrApplicationTag, kSecAttrKeyType,kSecAttrKeySizeInBits, kSecAttrEffectiveKeySize |
kSecClassIdentity | kSecClassKey,kSecClassCertificate |
iOS的Keychain由系统管理并且进行加密,Keychain内的信息会随着iPhone的数据一起备份。但是kSecAttrAccessible 属性被设置为后缀是ThisDeviceOnly的数据会被以硬件相关的密钥(key)加密。并且不会随着备份移动至其他设备。
kSecAttrAccessible变量用来指定这条信息的保护程度。我们需要对这个选项特别注意,并且使用最严格的选项。这个键(key)可以设置6种值。
CFTypeRef kSecAttrAccessible;//可访问性类型透明
kSecAttrAccessibleWhenUnlocked;//解锁可访问,备份
kSecAttrAccessibleAfterFirstUnlock;//第一次解锁后可访问,备份
kSecAttrAccessibleAlways;//一直可访问,备份
kSecAttrAccessibleWhenUnlockedThisDeviceOnly;//解锁可访问,不备份
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;//第一次解锁后可访问,不备份
kSecAttrAccessibleAlwaysThisDeviceOnly;//一直可访问,不备份
Keychain从iOS7.0开始也支持iCloud备份。把kSecAttrSynchronizable属性设置为@YES,这样后Keychain就能被iCloud备份并且跨设备分享。
不过在添加kSecAttrSynchronizable属性后,这条属性会被作为每条Keychain Item的主要的Key之一,所以在搜索,更新,删除的时候如果查询字典内没有这一条属性,item就匹配不到。
keychain使用:
实现uuid的保持、保持私密信息(不要直接存放未加密的重要账户密码信息)、
- (NSString *)getUUID
{
NSString * strUUID = [self searchKeychainCopyMatching:kUUIDKeychainIdentify];
//首次执行该方法时,uuid为空
if ([strUUID isEqualToString:@""] || !strUUID)
{
//生成一个uuid的方法
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef));
//将该uuid保存到keychain
NSMutableDictionary *dict = [self newSearchDictionary:kUUIDKeychainIdentify];
NSData *Data = [strUUID dataUsingEncoding:NSUTF8StringEncoding];
//这里要做一次加密,测试用就没有写
[dict setObject:Data forKey:(id)kSecValueData];
SecItemAdd((CFDictionaryRef)dict, NULL);
}
return strUUID;
}
3、keychain数据共享:
Keychain通过provisioning profile来区分不同的应用,provisioning文件内含有应用的bundle id和添加的access groups。不同的应用是完全无法访问其他应用保存在Keychain的信息,除非指定了同样的access group。指定了同样的group名称后,不同的应用间就可以分享保存在Keychain内的信息。
-
首先要在Capabilities下打开工程的Keychain Sharing按钮。然后需要分享Keychain的不同应用添 加相同的Group名称。Xcode6以后Group可以随便命名,不需要加AppIdentifierPrefix前缀,并且Xcode会在以entitlements结尾的文件内自动添加所有Group名称,然后在每一个Group前自动加上$(AppIdentifierPrefix)前缀。虽然文档内提到还需要添加一个包含group的.plist文件,其实它和.entitlements文件是同样的作用,所以不需要重复添加。 但是每个不同的应用第一条Group最好以自己的bundleID命名,因为如果entitlements文件内已经有Keychain Access Groups数组后item的Group属性默认就为数组内的第一条Grop。
-
需要支持跨设备分享的Keychain item添加一条AccessGroup属性,不过代码里Group名称一定要加上AppIdentifierPrefix前缀。
[searchDictionary setObject:@“AppIdentifierPrefix.UC.testWriteKeychainSuit” forKey:(id)kSecAttrAccessGroup];
如果要在app内部存私有的信息,group置为自己的bundleID即可,如果entitlements文件内没有指定Keychain Access Groups数组。那group也可以置为nil,这样默认也会以自己的bundleID作为Group。
iOS 应用间共享 Keychain 数据:
如果我们有多个APP,他们之间又需要互相共享一下数据,那么我可以考虑下使用keychain进行数据共享。
1、开启Keychain Sharing ,TARGETS - Capabilities - Keychain Sharing
2、开启后会在工程中生成一份entitlements,表示keychain access groups, keychain access groups和 entitlements文件直接编辑 都可以修改,内容为group的名称,格式如下:
默认生成如下:
$(AppIdentifierPrefix) + 你的Bundle ID
可以自定义用于创建独立组,自定义格式为:
自定义内容 //但是如果需要分享的组格式需要如下:
$(AppIdentifierPrefix) + 自定义内容
最后还需要提醒一点的是,如果你的帐号有多个AppIdentifierPrefix(team帐号),一需要确认一下你的mobileprovision文件里面的keychain-access-groups是否和你的keychain.entitlements文件定义的一致,否则xcode会报错。具体查看mobileprovision的方法是:命令行cat一下
这样你们既有自己私有的空间也有公共的空间
有相同的 Team ID,这个是应用间共享 Keychain 数据的前提条件。一个 App ID 分两部分:
- Apple 为你生成的 Team ID
- 开发者注册的 Bundle ID
一个典型的 App ID 如:659823F3DC53.com.apple.oneappleapp。
659823F3DC53 即为你的 Team ID,是 Apple 为你生成的。一个开发者账号可以有不同的几个 Team ID。但 Apple 不会为不同的开发者生成一样的 Team ID。这样,不同的开发者账号发布的应用想共享 keychain 数据,在现在来看是无法实现的。而要做到 keychain 数据共享,要求是同一个开发账号开发的,同时选择了相同的 Team ID。
//bundleSeedID 也就是我们需要的Team ID
- (NSString *)bundleSeedID {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
@"bundleSeedID", kSecAttrAccount,
@"", kSecAttrService,
(id)kCFBooleanTrue, kSecReturnAttributes,
nil];
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess)
return nil;
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
CFRelease(result);
return bundleSeedID;
}
iOS应用安全机制
keychain
http://www.jianshu.com/p/3afc39f6b9a8
http://blog.csdn.net/ibcker/article/details/24839143
http://www.cocoachina.com/ios/20161129/18215.html
在开启 keychain的时候,生成的entitlements默认生成了一个item,填写的是当前app的bundleID,但其实应该是TeamID,前面还有前缀:AppIdentifierPrefix
例如: 98XXXXXXAXTT.com.yang.OCDemoTwo
默认写入的keychain在entitlements的第一项,加入有一部分内容你不希望分享给兄弟app,那么就可以单独设立一个share的group专门用于分享内容
entitlements 中写入的item只要共享数据的app填写相同即可,不需要特殊,
如果在默认情况下,app默认是存在当前teamID这个名字的group下面,所以如果两个App希望全部共享对方的keychain内容,则只需要把两个app的teamID 都填入keychain groups即可。
正常情况下我们会开启独立分享组:
第一项item为自己的teamID,第二项为我们需要共享的keychain组组名
当我们需要分享时,把内容指定存入分享组内,不需要分享时默认存放即可
keychain 维持唯一UUID
keychain 存放在哪?
codesign
[self haveBundleIdentifier];//获取BundleIdentifier
[self haveappidentifierprefix];//获取appidentifierprefix
[self haveBundleExecutable];//获取项目名称
- (void)haveBundleExecutable{
- (void)haveappidentifierprefix{
NSString * prefix = [self bundleSeedID];
NSLog(@"prefix-->%@",prefix);
}
- (NSString *)bundleSeedID {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
@"bundleSeedID", kSecAttrAccount,
@"", kSecAttrService,
(id)kCFBooleanTrue, kSecReturnAttributes,
nil];
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess)
return nil;
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
CFRelease(result);
return bundleSeedID;
}
- (void)haveBundleIdentifier{
NSString * identifier = [[NSBundle mainBundle]bundleIdentifier];
NSLog(@"identifier--->%@",identifier);
}
- (void)haveBundleExecutable{
NSString *executableFile = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleExecutableKey]; //获取项目名称
NSLog(@"executableFile-->%@",executableFile);
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey]; //获取项目版本号
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSLog(@"infoDictionary-->%@",infoDictionary);
// app名称
NSString *app_Name = [infoDictionary objectForKey:@"CFBundleDisplayName"];
// app版本
NSString *app_Version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
// app build版本
NSString *app_build = [infoDictionary objectForKey:@"CFBundleVersion"];
}
4、keychain的安全性
Keychain内部的数据会自动加密。如果设备没有越狱并且不暴力破解,keychain确实很安全。但是越狱后的设备,keychain就很危险了。
通过上面的一些信息我们已经知道访问keychain里面的数据需要和app一样的证书或者获得access group的名称。设备越狱后相当于对苹果做签名检查的地方打了个补丁,伪造一个证书的app也能正常使用,并且加上Keychain Dumper这些工具获取Keychain内的信息会非常容易。
5、使用keychain需要注意的问题
- 当我们不支持Keychain Access Group,并且没有entitlement文件时,keychain默认以bundle id为group。如果我们在版本更新的时候改变了bundle id,那么新版本就访问不了旧版本的keychain信息了。解决办法是从一开始我们就打开KeychainSharing,添加Keychain Access Group,并且指定每条keychain Item的group,私有的信息就指定app的bundle id为它的group。
- 代码内Access group名称一定要有AppIdentifierPrefix前缀。
- Keychain是基于数据库存储,不允许添加重复的条目。所以每条item都必须指定对应的唯一标识符也就是那些主要的key,如果Key指定不正确,可能会出现添加后查找不到的问题。
- kSecAttrSynchronizable也会作为主要的key之一。它的value值默认为No,如果之前添加的item此条属性为YES,在搜索,更新,删除的时候必须添加此条属性才能查找到之前添加的item。
- Kechain item字典内添加自定义key时会出现参数不合法的错误。
https://www.apple.com/business/docs/iOS_Security_Guide.pdf
http://blog.csdn.net/ibcker/article/details/24839143
http://www.jianshu.com/p/72c1f9d3a58c
https://www.cnblogs.com/Jenaral/p/5663096.html