1. KeyChain 简要概述

KeyChain的实质是一个安全的数据库(数据库位于/private/var/Keychains/keychain-2.db),里面大部分数据都是加密的, 我们可以用KeyChain保存一些私密信息,比如密码、证书、设备唯一码(UDID)等等,总的来说KeyChain有如下特点:

  1. 安全
  2. Keychain的信息是存在于每个应用沙盒之外由系统负责管理,因此不会因App删除而丢失,在重装App后,Keychain里的数据还能使用,除非将系统恢复出厂设置。
  3. 一般而言不同App之间Keychain不能相互访问,但是从iOS 3 开始可以通过设置共有钥匙串部分信息。可以利用这一特性在自家的应用上共享KeyChain信息。
2. KeyChain的结构

每一个KeyChain由多个KeyChain item组成,KeyChain item的结构类似字典,同时每条KeyChain Item还包含一条data和多个attributes组成。
其中苹果提供了下面几种类型的keychain item,并且对不同类型的item做了不同的处理,比如password和key类的item就会做加密,而certificates类的就不会。

  • kSecClass

KeyChain Item 的类别,可以是下面几项:

extern CFTypeRef kSecClassGenericPassword   //通用密码项
extern CFTypeRef kSecClassInternetPassword //互联网密码项
extern CFTypeRef kSecClassCertificate //证书项
extern CFTypeRef kSecClassKey //key项
extern CFTypeRef kSecClassIdentity //认证项

系统指定的这些item都有特定需要配置的属性,这些属性是可选的不一定都需要给定,详细的可以查看SecItem.h文件,下面仅仅列出kSecClassGenericPassword的Attribute.

kSecClassGenericPassword item attributes:
kSecAttrAccess (OS X only)
kSecAttrAccessControl
kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified)
kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified)
kSecAttrCreationDate
kSecAttrModificationDate
kSecAttrDescription
kSecAttrComment
kSecAttrCreator
kSecAttrType
kSecAttrLabel
kSecAttrIsInvisible
kSecAttrIsNegative
kSecAttrAccount
kSecAttrService
kSecAttrGeneric
kSecAttrSynchronizable

需要注意的kSecClassIdentity item 由于是私有key和证书的结合,因此它的Attribute是二者的合集。

其中还有个比较重要的Attribute是kSecAttrAccessible,在我们调用SecItemCopyMatching方法返回item数据的时候,如果权限不够就会抛出errSecInteractionNotAllowed的错误。

kSecAttrAccessibleWhenUnlocked     当前的Item只有设备处于解锁状态才能被访问,这个适用于在前台访问的Item.
kSecAttrAccessibleAfterFirstUnlock 当前的Item只有设备重启并解锁后才能被访问,这个适用于需要在后台访问的Item.
kSecAttrAccessibleAlways 不论是否解锁库,都能访问改item,这种类型极为不安全,不推荐使用。

上面这些类型在加密备份的时候都会同步到新的机器上。

kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
kSecAttrAccessibleAlwaysThisDeviceOnly

上面这些多出了ThisDeviceOnly,这个表明这些Item在加密备份后,会被以硬件相关的密钥(key)加密。并且不会随着备份移动至其他设备。

一般而言最好不要将Item设置为kSecAttrAccessibleAlways,并且钥匙串可以通过iTunes或iCloud同步的方式同步到其他设备,如果你保存的数据高度敏感,则需要使用后缀为ThisDeviceOnly的选项。

Keychain 从 iOS 7.0 开始也支持iCloud备份。把kSecAttrSynchronizable属性设置为@YES,这样后Keychain就能被iCloud备份并且跨设备分享。

3. 在项目中引入KeyChain的方案:

不论使用下面哪种方式在使用之前都必须往项目中导入Security.framework框架

对于KeyChain 的封装较少目前网上用得较多的有如下三种方式:

  1. 使用官方推出的 KeychainItemWrapper

使用KeychainItemWrapper保存数据:

KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"idealists" accessGroup:nil];
NSString *username = @"linxiaohai";
NSString *password = @"123";
[wrapper setObject:username forKey:(id)kSecAttrAccount];
[wrapper setObject:password forKey:(id)kSecValueData];

在初始化KeychainItemWrapper会用到两个参数:

  • Identifier: 我们从keychain中取数据的时候会用到
  • accessGroup: 如果想要在应用之间共享信息,那么需要指定访问组KeyChain Access Group.如果不需要共享则传nil
  • (void)setObject:(id)inObject forKey:(id)key;
    这里的key必须是Security.framework 里头文件“SecItem.h”里定义好的key,用其他字符串做key程序会崩溃

使用KeychainItemWrapper获取数据:

KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"idealists" accessGroup:nil];
username = [wrapper objectForKey:(id)kSecAttrAccount];
password = [wrapper objectForKey:(id)kSecValueData];

这里需要注意的是identifier和accessGroup必须要对应上。

  1. 第三方封装SAMKeychain,SSKeychain
    这两个三方库其实是同一个实现,只不过在iOS 10中有个私有系统类也叫SSKeychain,所以如果在iOS 10中使用会有不兼容的问题,可以查看对应的issue

    这个是star 比较多的一个开源项目,接口也十分简单。

  2. 通过Security.framework框架使用
    不论是KeychainItemWrapper还是SAMKeychain都是对Security的封装,Security.framework提供了如下的API供我们使用:

    SecItemAdd 添加一个keychain item
    SecItemUpdate 修改一个keychain item
    SecItemCopyMatching 搜索一个keychain item
    SecItemDelete 删除一个keychain item
4. 使用KeyChain Access Group 实现不同APP共享Keychain中的数据

从iOS 3.0 之后,不同应用之间可以共享KeyChain 数据了。如果我们的产品线有一系列的应用并且这些应用之间需要共享一些公共的账号信息,就可以通过共享KeyChain来实现。但是这是有严格限制的,只有拥有相同 App ID 前缀的应用才有可能共享 keychain。并且各应用存储的 keychain item 都需要标记了相同的 kSecAccessGroup 字段值。

App ID 是由两个部分组成:

<Bundle Seed ID> . <Bundle  Identifier>

Bundle Seed ID(Team ID) 是由苹果公司在第一次创建一个App ID生成的,是一个唯一的10个字符组成的字符串。Bundle Identifier这个就是我们应用的bundle id。
比如:

659823F3DC53.com.example.amazingApp

659823F3DC53 是我们的Team ID。一个开发者账号可以有几个不同的Team ID。也就是说要共享数据必须要求使用同一个Team ID。

比如我们的两个应用App ID如下:

ABC1234DEF.com.useyourloaf.amazingApp1
ABC1234DEF.com.useyourloaf.amazingApp2

我们可以定义一个共享的KeyChain Access Group

ABC1234DEF.amazingAppFamily
  1. Project-> Capebilities-> Keychain Sharing ,将Keychain Sharing打开,在新版的Xcode中,将Keychain Sharing打开后,会在项目对应的目录下自动生成对应的Entitlements文件,在Entitlements文件的KeyChain Access Group节点中添加KeyChain Access Group名字。ABC1234DEF.amazingAppFamily

  1. 在 Project-> Build Setting -> Code Signing Entitlements 中添加上一个步骤生成的Entitlements文件。

  2. 在存储数据的时候指定kSecAttrAccessGroup 为 ABC1234DEF.amazingAppFamily

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。
  • KeyChain item字典内添加自定义key时会出现参数不合法的错误,所以要注意对传入的参数进行校验。
6. Keychain的安全性

Keychain内部的数据会自动加密。如果设备没有越狱并且不暴力破解,Keychain确实很安全。但是越狱后的设备,Keychain就很危险了,结合Keychain Dumper等工具,很容易拿到Keychain数据,网上也有较多现成攻略,

6. KeyChain的进一步封装

SAMKeychain 是一个很不错的项目,但是它只提供密码形式的存储,这里向借鉴SAMKeychain对KeyChain的核心功能的封装以及Masory配置属性的方式(主要是克服使用字典方式传递参数的时候,如果key不是指定的会崩溃的问题)

主要分成四层:

  1. KeyChainAttibuteMaker:将kSecClass传递进去KeyChainAttibuteMaker根据kSecClass,新建出对应的KeyChainItemAttribute的子项,比如传入kSecClassGenericPassword会新建一个GenericPasswordAttribute,并通过Block传出,供外面对属性进行配置。

  2. KeyChainAttibuteMaker会对配置完的KeyChainItemAttribute对象进行校验,对于非nil类型的属性,根据属性名,获取Keychain Attribute 的key字符串,构建出NSDirectionary对象传递出来。

  3. NMKeyChainTool 提供顶层的封装供外部开发者调用,它主要完成两项任务: KeyChainItemAttribute 的构建,KeyChainItemAttribute转NSDirectionary 调用NMKeyChainItemQuery操作KeyChain

  4. NMKeyChainItemQuery 是比较纯粹的增删改查的操作。调用的是Security.framework的SecItemAdd,SecItemUpdate,SecItemCopyMatching,SecItemDelete对KeyChain进行操作。

Contents
  1. 1. 1. KeyChain 简要概述
  2. 2. 2. KeyChain的结构
  3. 3. 3. 在项目中引入KeyChain的方案:
  4. 4. 4. 使用KeyChain Access Group 实现不同APP共享Keychain中的数据
  5. 5. 5. 使用keychain需要注意的问题
  6. 6. 6. Keychain的安全性
  7. 7. 6. KeyChain的进一步封装