iOS 打包签名,你真的懂吗
导语:iOS 签名类型有 Development、AD-Hoc、In-House、App Store,而打包过程中又涉及到各种证书、Provision Profile、entitlements、CertificateSigningRequest、p12、AppID......各种概念一大堆,本文将从打包签名的原理说起,并梳理完全签名的整体流程,最后讲解重签名的实现以及签名机制中有哪些是需要注意防护的要点。
为了保证 App 的分发平台是可控的,以及保证所有安装到 iOS 设备上的 App 都是经过苹果官方允许的,苹果建立了 iOS 签名打包机制。要了解 iOS 签名机制的实现,我们首先从签名机制的原理说起。
1. 签名原理
1.1 不对称加密
网络数据的传输可以使用对称加密以及不对称加密的方式进行安全防护,对称加密是指数据发送者(A)和接收者(B)双方进行加解密的密钥是一致的,但这样会增加密钥自身分发的不安全性:比如要如何保证密钥在传递过程中不被泄露。
而不对称加密则由 A、B 持有一对公私钥进行加解密,公私钥钥匙对是成对出现的。对于一个私钥,有且只有一个与其对应的公钥,私钥保密、公钥公开,但是不能通过公钥推导出私钥,使用私钥加密的文件可用公钥解密,反过来公钥加密的文件也只能用私钥进行解密。加密过程如下:
发送方(A)首先生成一对公私钥钥匙对,私钥自己保管,公钥则任意分发出去(每台 iOS 设备终端其实已经包含 Apple 的公钥)。
发送数据时,发送方使用私钥对原数据加密成密文传输(加密打包 ipa);
接收方(B)收到密文后,使用之前已经获取到的公钥进行解密得到数据内容(iOS 设备验证安装 ipa)。

1.2 数据签名
这里主要解决了两个问题,一个是加密数据大小的问题,另一个是如何验证公钥的有效性。
1.2.1 信息摘要
前面已经讲到,iOS 打包安装的过程中会对 ipa 包进行加解密验证。然而 ipa 安装包大小动辄就有十几 M,大的有好几 G,那如果对这么大的数据量进行加解密,肯定效率是非常低的。而信息摘要则是解决了加密数据过大的问题,其原理是对信息内容通过一个很难被逆向推导的公式计算得到一段哈希数值,它具有以下特点:
计算得到的哈希值大小固定,不受原本信息内容大小的影响;
不可逆,根据哈希值无法推断得到原本信息(实际上 MD5 以及 SHA-1 算法已经被证明可以被破解);
唯一性,原本信息内容一致,那么哈希值也一致;原数据不同,也不会存在重复的哈希结果。
使用信息摘要技术在数据加密传输时,发送方先对文件内容使用哈希算法进行信息摘要计算,再对摘要内容进行加密,之后将文件内容以及摘要内容(已加密)发送出去。
接收方收到数据后,先解密得到摘要内容,再依据相同的哈希算法对文件内容进行信息摘要计算,最后匹配接收到的哈希值与计算得到的哈希值是否一致,如果一致那就说明传输过程是安全的。
这样也就避免了对整体原数据加解密的计算过程,从而提高了验证效率。
1.2.2 签名证书
不对称加密中的公钥是公开的,谁都可以得到,这样也就存在了不安全性。比如主动攻击者 C 冒充数据发送者 A,将自己伪装后的公钥分发给数据接收者 B,从而达到监听 A、B 之间通信的目的,又或者是对 A、B 之间的通信数据进行注入攻击。

那为了保证获取公钥的安全性,这里引入 CA 认证(Certificate Authority)。CA 是证明公钥合法性的权威机构(Apple 就属于 CA 认证机构),它为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。用户使用 CA 的公钥对数字证书上的签名进行验证,如果验证通过,也就认为证书内包含的公钥是有效的。

CA 认证确保了用户公钥使用过程中的安全性,iOS 打包需要向苹果开发者中心上传.certSigningRequest
文件,然后配置得到各种.cer
证书,这些流程中便包括了开发者向 Apple CA 认证中心注册公钥的过程。
2. iOS 签名
2.1 概念要点
.certSigningRequest 文件。从 Mac 的钥匙串访问中生成
.certSigningRequest
文件,这个过程会从 Mac 终端生成一对钥匙对,私钥存储在 Mac 中,公钥则包含在.certSigningRequest
中。再将.certSigningRequest
文件上传到 Apple 后台即苹果开发者中心,则可以对应生成开发证书或者发布证书(.cer
文件)。.cer 文件:Apple 后台使用 Apple 私钥对 Mac 公钥进行签名后生成的证书。
.p12 文件:Mac 本地生成的钥匙对私钥。由于私钥是本地私有的,但你可以使用
.p12
将私钥导出给其他团队成员使用。Identifiers。
Identifiers
是 iOS 设备安装应用时用来识别不同 App 的唯一标识,点击创建 App IDs,同时勾选 app 所包含的权限:APNs、HealthKit、iCloud 等。entitlements。App 使用到的各种权限(APNs、HealthKit、iCloud 等),也是需要 Apple 验证通过后才能生效的,Apple 将这些权限开关统一称为 Entitlements。当第一次在 Xcode 中勾选权限时,项目中会自动生成一个.entitlements 后缀的文件,里面记录了 App 所拥有的权限。
Profiles。
.cer
文件只是声明了证书的类型,比如 Apple Development、Apple Distribution、APNs 推送等等,而至于使用什么证书打包、AppID 是什么、打包的 App 包含了哪些功能、可以在哪些设备上安装,则是通过Provisioning Profile
描述文件(.mobileprovision
后缀)来说明的,苹果后台将所有这些信息组合后再使用 Apple 私钥进行签名,最后生成Provisioning Profile
描述文件:
2.2 AppStore 签名
发布 App 至 AppStore 之前需要经过苹果后台审核,审核通过苹果后台会用 Apple 私钥对 App 数据进行加密签名生成 ipa 包;用户从 AppStore 下载 App 后,使用设备内置的 Apple 公钥解密验证,验证通过安装成功。由于 AppStore 分发的过程中上传审核、下载安装的整个过程都处在苹果的生态链内,所以只需要一次验证就能保证安全性。

2.3 其他签名
从 AppStore 下载安装 App 只需要一次数字签名就足以保证安全性,但除了这种途径苹果还有其他的安装方式:
开发中连接设备到 Xcode 进行调试安装
AD-Hoc 内部测试安装,需要先获取设备 UDID 并注册,并且有最多 100 台设备的限制
In-House 企业内部分发,安装设备数量无限制,但安装后需主动在设置中选择信任证书
那这些安装 App 的过程中苹果又是怎样保证流程安全性的呢?答案就是双重签名机制,苹果使用前面讲到的 Mac 本地钥匙对以及 Apple 后台钥匙对进行多次数字签名,从而保证整体流程的可控。
Mac
钥匙串访问
在本地生成一对公私钥钥匙对,下面默认为公钥 L、私钥 L(L:Local)。Apple 已有一对公私钥钥匙对,私钥 A 在 Apple 后台,公钥 A 内置到每一台 iOS 设备终端(A:Apple)。
上传公钥 L 至 Apple 后台,使用私钥 A 对公钥 L 进行数字签名生成签名证书**.cer**,同时使用私钥 A 对额外信息(使用什么证书打包、AppID、打包的 App 包含了哪些功能、可以在哪些设备上安装)进行签名生成描述文件 Provisioning Profile,之后将**.cer 和 Provisioning Profile**下载安装到 Mac 机器上。
编译打包 app,选择签名证书**.cer**,打包指令会自动找到该证书对应的私钥 L(能匹配是因为钥匙对是成对出现的,前提是本地必须已经存在 L 私钥,也就是 p12 的安装),然后使用私钥 L 对 app 进行签名。
这些签名数据包含两部分:
Mach-O
可执行文件会把签名直接写入这个文件中,其他资源文件则会保存在_CodeSignature
目录下。你可以将打包生成的.ipa
文件另存为.zip
,解压后对Payload
文件夹中的.app
文件右键、显示包内容,就可以看到签名数据。另外签名过程中对于 App 内包含的动态库以及插件(
Plugins、Watch、Frameworks
文件夹),每一个都会单独进行一次签名,并生成各自的Mach-O
可执行文件和_CodeSignature
。签名数据指代码内容、App 包含的所有资源文件,只要其中有任何改动,都必须重新签名才有效。
打包的过程中会将描述文件 Provisioning Profile 命名为 embedded.mobileprovision 放入到打包 app 中。
安装/启动,iOS 设备使用内置的公钥 A 验证 embedded.mobileprovision 是否有效(设备是否在允许安装列表内),同时再次验证里面包含的**.cer 证书签名是否有效(证书过期与否)并取出公钥 L**。
embedded.mobileprovision 验证通过,就使用公钥 L 解密验证 app 签名信息:AppID 是否对应、权限开关是否跟 app 里的
entitlements
一致等等。所有验证通过,安装/启动完成。

以上流程便是开发调试、AD-Hoc、In-House 等方式打包安装 App 的过程,区别只在于第⑤步中设备IDs
的匹配规则不一致。开发调试只安装当前联调的设备;AD-Hoc 允许安装到已在开发者账号下注册过的设备,且每年最多允许 100 台;In-House 无设备数量限制,常用于企业内部 App 的分发。
3. ipa 包重签名
ipa 包重签名主要针对的是非 App Store 的安装包,App Store 分发最终是上传 ipa 文件到苹果后台审核,通过后使用 Apple 私钥加密,然后才能发布安装,不存在重签入侵的可能。而开发调试、AD-Hoc、In-House 等分发途径生成的 ipa 包不存在苹果后台验证的步骤,这也就意味着你可以对任意的.app、
.ipa
文件进行重签名。
回顾前面讲到的签名流程,真正对 ipa 包进行签名的关键步骤(④⑤)是在 Mac 本地进行的,签名过程中需要满足三个条件:App
即软件代码编译生成的产物、p12
证书以及Provisioning Profile
配置文件。其中App
的内容是动态变动的,Apple 不会去验证它,实际上也无需验证,因为在开发调试过程中,所开发的 App 肯定是不停的迭代变化的,如果需要上线 App Store 那 Apple 只需在审核阶段对 App 内容进行把关验证即可,而其他分发渠道它则管不了。p12
以及Provisioning Profile
则是下载后主动安装的,大部分情况下都是由管理员创建下载好之后,导出分发给团队成员。
3.1 签名指令
iOS 签名调用的是 codesign 指令,你也可以直接使用相关指令进行签名,下面是 codesign 的常用指令:
查看 Xcode 的编译日志,也可以看到签名的详细信息

3.2 重签名
首先获取需要重签名的 ipa 包,注意该 ipa 包必须是未加密的。如果是从 App Store 下载的 ipa,需要砸壳解密后才能进行重签名,你也可以从越狱平台下载。将获取的
.ipa
重命名为.zip
,然后右键解压,将会生成一个 Payload 文件夹,里面包含.app
文件。将签名证书对应的
Provisioning Profile
文件重命名为 embedded.mobileprovision,并拷贝放到 Payload 文件夹中。同时右键.app
文件,显示包内容,将前面的 embedded.mobileprovision 文件再拷贝一份放到.app
文件夹中,替换掉原有的embedded.mobileprovision
。entitlements.plist 是由签名证书对应的 Profile 配置导出的签名文件,它与前面截图 Xcode 签名日志中的
XXX.xcent
文件的作用相同。终端 cd 到 Payload 文件夹路径,执行指令
将会打印出 Profile 配置的内容,找到<key>Entitlements</key>
,然后把<key>Entitlements</key>
下面<dict>...</dict>
的内容拷贝到新建的entitlements.plist
文件中(可以通过 Xcode 生成 plist 文件,选 Property List 类型),最后将entitlements.plist
文件放到 Payload 文件夹中。
签名证书名称可以在安装证书后从钥匙串中心查看
或者在终端使用以下指令查看:
准备工作完成,开始重签名。先右键
.app
显示包内容,查看动态库和插件(Plugins、Watch、Frameworks
文件夹),如果是个人证书需要移除Plugins、Watch
文件夹,因为个人证书没法签名 Extention。如果存在Frameworks
,则执行签名指令,有多个的话则每一个 Frameworks 都要重签一次。
最后对 app 进行重签名
最后将 Payload 文件夹下的资源移除,只保留**.app**文件,右键压缩,然后更改后缀为
.ipa
,这样重签后的 ipa 便已生成了,你可以通过 iTunes、iTools 或其他途径安装到 iOS 设备上。
3.3 注入代码重签
ipa 代码注入一般通过动态库来实现。新建动态库在 Xcode 中选择新建
TARTETS — Framework & Library — Framework
,然后在 framework 中添加自定义代码,一般都是使用 Runtime 来注入附加功能。最后选择 framework 要支持的架构,编译后便得到了最终动态库。对需要重签名的.app 右键显示包内容,然后将动态库拷贝到
Framework
文件夹(没有则新建)中。然而此时动态库与 app 还没建立关联关系,动态库需要注入MachO
中才能生效。注入使用 yololib工具,下载 yololib 并编译,将生产的命令复制到/usr/local/bin
或$PATH
中的其他路径,便可以在终端使用yololib
指令
注入成功后再对所有 Framework 签名,最后对 app 重签名,然后生成 ipa 文件。
这里整理了一份用于重签名的脚本 CJCodeSign,想了解更多关于签名指令的内容可点击查看详情。
3.4 关于重签名的思考
iOS 重签名实现,可以发现用于签名的私钥资源(包括.cer
证书和Provisioning Profile
配置)和实际签名的 app 包是没有强关联关系的,这也就带来了两方面的问题。
.cer
证书和Provisioning Profile
配置被用于其他 App 的分发签名,特别如果是 In-House 企业类型的证书,那是可以进行无限制分发的,而一旦苹果检测到这种违规签名的行为,轻则撤销证书,重则注销企业开发者账号!这也就是为什么一定要严格把控p12
、Provisioning Profile
文件外发的原因。自有 App 被注入代码后重签名,比如应用多开、添加插件、恶意抓包等等,对于这一类的防护除了对 Bundle ID 进行检查,以及对 App 动态库增加白名单检索外好像也没有更好的办法。当然这已经涉及到逆向防护的方向了,本人对此还未深入了解,有兴趣的同学可以一起参与探讨。
全文完!
最后再附上重签名脚本地址: CJCodeSign
作者GitHub地址。
版权声明: 本文为 InfoQ 作者【Geen练】的原创文章。
原文链接:【http://xie.infoq.cn/article/0b074a263d68df05dd78b48d3】。文章转载请联系作者。
评论