iOS 中使用 Keychain Services 在应用间共享数据
2014-08-10
iOS 中使用 Keychain Services 在应用间共享数据
iOS 系统中,各个应用被限制在沙盒(sandbox)中运行。这样的设计,应用的健壮性和安全性都得到了提高,但是,应用间共享信息就变得不那么容易。在 iOS 3.0 之后,应用间共享数据成为了一种可能。但仍然是被严格限制的,只有拥有相同 App ID 前缀的应用才有可能通过 keychain service 共享简单数据。通过这个工具,你可以安全地在一些的应用间共享用户登录凭据,从而免除用户重复输入的麻烦。
Keychain Services 基本概念
Keychain Services 是 OS X 和 iOS 都提供一种安全地存储敏感信息的工具,比如,存储用户ID,密码,和证书等。存储这些信息可以免除用户重复输入用户名和密码的过程。Keychain Services 的安全机制保证了存储这些敏感信息不会被窃取。简单说来,Keychain 就是一个安全容器。
Keychain 的结构
Keychain 可以包含任意数量的 keychain item。每一个 keychain item 包含数据和一组属性。对于需要保护的 keychain item,例如,密码或者私钥,数据是加密的,会被 keychain 保护起来的;对于无需保护的 keychain item,例如,证书,数据未被加密。
与 keychain item 联系的属性取决于 item 的类型。经常被使用到的 item 类型是 Internet passwords 和 generic passwords。如你期待的那样,Internet passwords 包含了诸如安全域,协议类型,路径之类的属性。Passwords 以加密的形式存储在 keychain item 中。在 OS X 中,当 keychain 被锁之后,加密的 item 是不能访问的。如果你试图访问一个被锁住的 item,Keychain Services 将显示一个对话框要求用户输入 keychain 的密码。属性是都不加密的,它们可以在任何时候被读取,即使是在 keychain 被锁住了的情况。在 iOS 中,应用总是可以访问自己所拥有的 keychain items。
iOS 中的 Keychain Services
强调一下 iOS 中的状况,应用总是可以访问自己拥有的 keychain items,而不能访问其他应用的 keychain item。系统将会为 keychain 生成密码,并将密码以任何应用都无法访问的方式存储。当一个用户备份 iPhone 数据时,keychain 数据也会被备份,但是 keychain 中的敏感数据仍然是加密的。keychain 的密码则不会被备份。因此,即使某人获取了备份数据的访问权限,他还是无法访问存储在 keychain 中的密码和其他敏感信息。因此,使用 keychain 存储敏感信息(密码,cookie 等)是很安全的。
Keychain 的操作
- SecItemCopyMatching
- SecItemAdd
- SecItemUpdate
- SecItemDelete
SecItemCopyMatching 用于搜索。其他操作操作,SecItemAdd,SecItemUpdate,SecItemDelete 从字面上就能理解。具体的使用细节可以查看 Apple 的文档:Apple Keychain Services Reference。
Apple 也提供了一个关于 Keychain 操作的封装:KeychainItemWrapper。在 Xcode 的 Document 中可搜到此项目。
github 上也有一个使用起来稍微简单一些的封装:PDKeychainBindingsController。但 PDKeychainBindingsController 不支持 Access Group。这在后面讲到的应用间共享 keychain 数据时需要用到,但是自己加上这个并不困难。
iOS 应用间共享 Keychain 数据
在 iOS 3.0 之后,应用间共享 keychain 数据成为了一种可能。但这是被严格限制的。只有拥有相同 App ID 前缀的应用才有可能共享 keychain。并且各应用存储的 keychain item 都需要标记了相同的 kSecAccessGroup 字段值。
相同的 Team ID
有相同的 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。
在 Apple 的 iOS Dev Center 中,创建 App ID 时,选择 Team ID 时,你会看到这样提示:
If you are creating a suite of applications that will share the same Keychain access, use the same bundle Seed ID for each of your application's App IDs.
其中提到的 Seed ID 即这里说的 Team ID。
com.apple.oneappleapp
即为你的 Bundle ID。 这个是由开发者决定的。
kSecAccessGroup
在存储 keychain item 时,需要明确存入你的 access group:
[spec setObject:[[self class] accessGroup] forKey:(__bridge id)kSecAttrAccessGroup];
access group 字段存入这样的 659823F3DC53.com.apple.account
字段。前面的一串字符:659823F3DC53
必须为 TEAM ID,后面字符串则可由开发者决定。
项目配置
你需要创建一个 plist 文件,可以是任何名称,比如 Apple 的 demo 项目 KeychainItemWrapper 中,将其命名为 KeychainItemWrapper.plist
这个名字可能比较清楚。其内容应该如下图所示:
然后需要在 Xcode 中配置这个文件的路径。具体配置位置为:Project->Build setting->Code Signing Entitlements,填入这个文件的相对路径,可以参考项目中已有的一个配置文件 plist 的位置来填写。这个如果配置不好,是无法与其他应用共享 Keychain 的。配置好后,须用你正式的证书签名编译才可通过,否则 Xcode 会弹框告诉你 code signing 有问题。