Keychain 浅析
什么是Keychain?
根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个在所有app之外的sqlite数据库。
如果我们手动把自己的私密信息加密,然后通过写文件保存在本地,再从本地取出不仅麻烦,而且私密信息也会随着App的删除而丢失。iOS的Keychain能完美的解决这些问题。并且从iOS 3.0开始,Keychain还支持跨程序分享。这样就极大的方便了用户。省去了很多要记忆密码的烦恼。
Structure of a 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就不会被加密。
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的用法
首先导入Security.framework 。
Keychain的API提供以下几个函数来操作Keychain
- SecItemAdd 添加一个keychain item
- SecItemUpdate 修改一个keychain item
- SecItemCopyMatching 搜索一个keychain item
- SecItemDelete 删除一个keychain item
也可以参考以下这段简单的代码来了解下Keychain API的用法。
- (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;
}
- (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:passwordData forKey:(id)kSecValueData];
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
[dictionary release];
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[updateDictionary setObject:passwordData forKey:(id)kSecValueData];
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
(CFDictionaryRef)updateDictionary);
[searchDictionary release];
[updateDictionary release];
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (void)deleteKeychainValue:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
SecItemDelete((CFDictionaryRef)searchDictionary);
[searchDictionary release];
}
Keychain API的用法稍微有点复杂。不过Apple自己也提供了一个封装了Keychain API的类: KeychainItemWrapper https://developer.apple.com/library/ios/samplecode/GenericKeychain/Introduction/Intro.html 虽然这个类封装了Keychain的API,但是不仅代码写的很不容易理解,而且里面也有不少的Bug。所以还是不建议使用。 目前发现这个类的1.2版存在的Bug为:
- 如果需要某个keychain item支持iCloud备份,添加kSecAttrSynchronizable属性之后,它并没有在第二次更新item或者搜索item的时候加上这一条,所以导致item已经存在但是它却获取不到。
- 类型为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 |
Keychain的备份
iOS的Keychain由系统管理并且进行加密,Keychain内的信息会随着iPhone的数据一起备份。但是kSecAttrAccessible 属性被设置为后缀是ThisDeviceOnly的数据会被以硬件相关的密钥(key)加密。并且不会随着备份移动至其他设备。
kSecAttrAccessiblein变量用来指定这条信息的保护程度。我们需要对这个选项特别注意,并且使用最严格的选项。这个键(key)可以设置6种值。
- CFTypeRef kSecAttrAccessibleWhenUnlocked;
- CFTypeRef kSecAttrAccessibleAfterFirstUnlock;
- CFTypeRef kSecAttrAccessibleAlways;
- CFTypeRef kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
- CFTypeRef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
- CFTypeRef kSecAttrAccessibleAlwaysThisDeviceOnly;
从iOS5.0开始kSecAttrAccessible默认为kSecAttrAccessibleWhenUnlocked。
Keychain从iOS7.0开始也支持iCloud备份。把kSecAttrSynchronizable属性设置为@YES,这样后Keychain就能被iCloud备份并且跨设备分享。
不过在添加kSecAttrSynchronizable属性后,这条属性会被作为每条Keychain Item的主要的Key之一,所以在搜索,更新,删除的时候如果查询字典内没有这一条属性,item就匹配不到。
Keychain Access Group
Keychain通过provisioning profile来区分不同的应用,provisioning文件内含有应用的bundle id和添加的access groups。不同的应用是完全无法访问其他应用保存在Keychain的信息,除非指定了同样的access group。指定了同样的group名称后,不同的应用间就可以分享保存在Keychain内的信息。
Keychain Access Group的使用方法:
首先要在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。
Keychain的安全性
Keychain内部的数据会自动加密。如果设备没有越狱并且不暴力破解,keychain确实很安全。但是越狱后的设备,keychain就很危险了。
通过上面的一些信息我们已经知道访问keychain里面的数据需要和app一样的证书或者获得access group的名称。设备越狱后相当于对苹果做签名检查的地方打了个补丁,伪造一个证书的app也能正常使用,并且加上Keychain Dumper这些工具获取Keychain内的信息会非常容易。
使用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时会出现参数不合法的错误。
总结
keychain很强大,是一个值得利用的工具,我们可以在保存密码或者证书的时候使用keychain,并且支持不同应用分享Keychain内的信息,或者支持iCloud备份跨设备分享,但是越狱版应用还是不建议使用。
参考链接:
1.http://blog.sheliw.com/blog/2015/02/16/keychain/
2.https://github.com/kishikawakatsumi/KeychainAccess
Keychain 浅析的更多相关文章
- 关于Keychain
1.Keychain 浅析 2.iOS的密码管理系统 Keychain的介绍和使用 3.iOS开发中,唯一标识的解决方案之keyChain+UUID
- 聊聊iOS Keychain
某天,小熊碰见这样一个错误 Couldn't update the Keychain Item问题处理 ,网上搜索了下网上很多解决方案,依然百撕不得骑姐.后来参考下面两篇文章.才发现是用法不正确,网上 ...
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 高性能IO模型浅析
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- netty5 HTTP协议栈浅析与实践
一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...
- iOS逆向工程之KeyChain与Snoop-it
今天博客的主题是Keychain, 在本篇博客中会通过一个登陆的Demo将用户名密码存入到KeyChain中,并且查看一下KeyChain中存的是什么东西,把这些内容给导出来.当然本篇博客的重点不是如 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler
熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...
随机推荐
- shell脚本学习(四)
1.文件权限 1.1 用户有一个称为setuid(S)的特殊权限,它出现在执行权限(x)的位置,setuid权限允许用户以拥有者的权限来执行可执行文件,即使这个可执行文件是由 其他用户运行的. 具有s ...
- vultr vps发布多用户管理功能
中国用户购买海外vps,有一个麻烦之处是付款环节,可能你没有visa信用卡,vultr和digitalocean和linode这类vps不支持支付宝,给一些朋友带来不便.由此产生的vps代购行业,其实 ...
- freemarker之list和map
第一次使用freemarker很不习惯,之前都是用velocity的. @RequestMapping("/free.htm") public ModelAndView hello ...
- PIVOT就是行转列,UNPIVOT就是列传行
PIVOT通过将表达式某一列中的唯一值转换为输出中的多个列来旋转表值表达式,并在必要时对最终输出中所需的任何其余列值执行聚合.UNPIVOT与PIVOT执行相反的操作,将表值表达式的列转换为列值. 通 ...
- do循环的100米自由落体
#include "stdio.h" void main() { float h=100.0,sum=100.0; ; do { sum=sum+h; h=h/; g++; }); ...
- PHP signal 信号
最早写php时,发现在终端执行一个php文件,会一直等待程序执行完成以后,终端才能继续下面的操作,若不小心按了下Ctrl+C会导致php程序退出,闭避免这种情况发生,将会使用php的系统编程,即sig ...
- 《Windows驱动开发技术详解》之编程加载NT式驱动
之前我们加载驱动都是利用INSTDRV这个应用,其原理是在注册表中写入相应的字段,这一节我们手动编写代码去加载驱动,其原理类似:
- MFC中MessageBeep与sndPlaySound播放声音函数使用
MessageBeep(0x00000000L); //用来播放系统默认音频文件,如0x00000000L为系统提示音,具体音频对应规则,请参照MSDN. sndPlaySound函数用来播放指 ...
- mvn 下载源码和javadoc
1. 下载源码,两种: . mvn dependency:sources . mvn eclipse:eclipse -DdownloadSources=true 2. 下载javadoc,两种: . ...
- hdu_4826_Labyrinth_2014百度之星(dp)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4826 题意:中文题,不解释 题解:dp搞,第一列只能从上往下走,所以先算出第一列的dp数组,然后开两个 ...