指南:iOS签名类型包括Development、AD-Hoc、In-House、App Store。包装过程包括各种证书、Provision Profile、entitlements等。
为了确保App的部署平台是可控的,并且iOS设备上安装的所有App都获得Apple的正式许可,APLE建立了iOS签名打包机制。要理解IOS签名机制的实现,首先从签名机制的原理开始。
1.签名原理
1.1非对称加密
网络数据的传输可以使用对称加密和不对称加密来确保安全性。对称加密是指数据发送者(A)和接收者(B)加密和解密的密钥一致。但是,这样会增加密钥本身的部署不稳定性。例如,如何防止密钥在传递过程中泄露。
不对称加密是A、B拥有一对公钥进行加密解密,公钥密钥对成对出现。对于私钥,只有一个对应的公钥,私钥是秘密的,公钥是公开的。但是,不能通过公钥提取私钥。用私钥加密的文件可以用公钥解密。相反,用公钥加密的文件只能用私钥解密。加密过程如下:
1.发件人(A)第一个老师有一对公钥,私钥由自己保管,公钥随机分发(每个iOS设备终端已包含苹果的公钥)。
2.传输数据时,发送者使用私钥将原始数据加密为密文传输(加密包IPA)。
3.收件人(B)收到密码后,使用以前收到的公钥解密数据内容(iOS设备验证安装IPA)。
1.2数据签名
这里主要解决了两个问题。一个是加密数据大小的问题,另一个是如何验证公钥的有效性。
1.2.1信息摘要
如上所述,在安装iOS软件包的过程中,将对IPA数据包进行加密和解密验证。但是IPA安装包大小动辄十几米,大的是几克,如果加密解密这么多数据,效率会很低。《信息文摘》是解决加密数据过大的问题,其原理是通过难以反向推断的公式计算信息内容的哈希值,具有以下特征:
计算的哈希值的大小是固定的,与原始信息内容的大小无关。不可逆转。不能根据散列值推断原始信息(事实上,MD5和SHA-1算法已被证明可以解密)。唯一性,如果原始信息内容匹配,散列值也匹配。相反,理论上,不同内容的原始数据可能发生碰撞,产生相同的哈希值(即上面提到的海侵),但碰撞概率的大小受哈希函数的影响。您可能认为这只是理论上的概率事件,并不影响iOS签名中信息摘要的使用。使用信息摘要技术加密数据时,发送者首先对文件内容使用哈希算法进行信息摘要计算,然后对摘要内容进行加密,最后发送文件内容和摘要内容(已加密)。
收件人收到数据后,首先解密摘要内容,然后根据相同的散列算法对文件内容进行信息摘要计算。确认上次接收的散列值与计算的散列值相匹配。如果匹配,则表示传输过程是安全的。
这样可以防止整个原始数据加密和解密的计算过程,从而提高验证效率。
1.2.2签名证书
不对称加密的公钥是任何人都可以使用的公开,因此不安全。例如,主动攻击者C冒充数据发送者A,然后将伪装的公钥分发给数据接收者B,以接收A、B之间的通信或注入A、B之间的通信数据。
为了确保获取公钥的安全性,引入了认证机构(CA)。CA是证明公钥合法性的权威机构(Apple属于CA认证机构),向使用公钥的每个用户颁发数字证书。数字证书用于证明证书中列出的用户合法拥有证书中列出的公钥。用户使用CA的公钥验证数字证书的签名,如果认证通过,则认为证书中包含的公钥有效。
CA验证可确保用户公钥使用过程中的安全性。iOS打包由苹果开发人员中心提供`.必须上传ce。
rtSigningRequest`文件,然后配置得到各种`.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后台钥匙对进行多次数字签名,从而保证整体流程的可控。
1. Mac 钥匙串访问在本地生成一对公私钥钥匙对,下面默认为公钥L、私钥L(L:Local)。
2. Apple已有一对公私钥钥匙对,私钥A在Apple后台,公钥A内置到每一台iOS设备终端(A:Apple)。
3. 上传 公钥L 至Apple后台,使用 私钥A 对 公钥L进行数字签名生成签名证书 .cer ,同时使用 私钥A 对额外信息(使用什么证书打包、AppID、打包的App包含了哪些功能、可以在哪些设备上安装)进行签名生成描述文件 Provisioning Profile,之后将 .cer和Provisioning Profile下载安装到Mac机器上。
4. 编译打包app,选择签名证书 .cer,打包指令会自动找到该证书对应的私钥L(能匹配是因为钥匙对是成对出现的,前提是本地必须已经存在L私钥,也就是p12的安装),然后使用私钥L对app进行签名。
- 这些签名数据包含两部分:Mach-O可执行文件会把签名直接写入这个文件中,其他资源文件则会保存在 _CodeSignature目录下。你可以将打包生成的 .ipa文件另存为 .zip,解压后对 Payload 文件夹中的 .app 文件右键、显示包内容,就可以看到签名数据。
- 另外签名过程中对于App内包含的动态库以及插件(Plugins、Watch、Frameworks文件夹),每一个都会单独进行一次签名,并生成各自的Mach-O可执行文件和_CodeSignature。
- 签名数据指代码内容、App包含的所有资源文件,只要其中有任何改动,都必须重新签名才有效。
5. 打包的过程中会将描述文件 Provisioning Profile 命名为 embedded.mobileprovision放入到打包app中。
6. 安装/启动,iOS设备使用内置的 公钥A 验证 embedded.mobileprovision 是否有效(设备是否在允许安装列表内),同时再次验证里面包含的 .cer 证书签名是否有效(证书过期与否)并取出 公钥L。
7. embedded.mobileprovision 验证通过,就使用 公钥L 解密验证app签名信息:AppID是否对应、权限开关是否跟app里的 entitlements 一致等等。
8. 所有验证通过,安装/启动完成。
以上流程便是开发调试、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的常用指令:
# MAC终端输入: codesign --help
codesign --help
Usage: codesign -s identity [-fv*] [-o flags] [-r reqs] [-i ident] path ... # sign
codesign -v [-v*] [-R=<req string>|-R <req file path>] path|[+]pid ... # verify
codesign -d [options] path ... # display contents
codesign -h pid ... # display hosting paths
查看Xcode的编译日志,也可以看到签名的详细信息
# 签名指令
codesign -f -s "iPhone Distribution: XXX(证书名称)" --entitlements en(Profile配置文件) XXX.app(签名app)
3.2 重签名
首先获取需要重签名的ipa包,注意该ipa包必须是未加密的。如果是从App Store下载的ipa,需要砸壳解密后才能进行重签名,你也可以从越狱平台下载。将获取的.ipa重命名为.zip,然后右键解压,将会生成一个 Payload 文件夹,里面包含.app文件。
将签名证书对应的Provisioning Profile文件重命名为 embedded.mobileprovision,并拷贝放到Payload文件夹中。同时右键.app文件,显示包内容,将前面的embedded.mobileprovision文件再拷贝一份放到.app文件夹中,替换掉原有的embedded.mobileprovision。
en是由签名证书对应的Profile配置导出的签名文件,它与前面截图Xcode签名日志中的XXX.xcent文件的作用相同。终端cd到Payload文件夹路径,执行指令:
# cd xxx/Payload,然后执行下面指令
security cms -D -i embedded.mobileprovision
将会打印出Profile配置的内容,找到<key>Entitlements</key>,然后把<key>Entitlements</key>下面<dict>...</dict>的内容拷贝到新建的en文件中(可以通过Xcode生成plist文件,选Property List类型),最后将en文件放到Payload文件夹中。
# 拷贝内容为:<dict> ... </dict>
<key>Entitlements</key>
<dict>
<key>application-identifier</key>
<string>xxx</string>
<key>keychain-access-groups</key>
<array>
<string>xxx</string>
</array>
<key>get-task-allow</key>
<false/>
<key>com.a;/key>
<string>xxx</string>
</dict>
签名证书名称可以在安装证书后从钥匙串中心查看
或者在终端使用以下指令查看:
security find-identity -v -p codesigning
准备工作完成,开始重签名。先右键.app显示包内容,查看动态库和插件(Plugins、Watch、Frameworks文件夹),如果是个人证书需要移除Plugins、Watch文件夹,因为个人证书没法签名Extention。如果存在Frameworks,则执行签名指令,有多个的话则每一个Frameworks都要重签一次。
codesign -fs "签名证书名称" "Framework(动态库路径)"
最后对app进行重签名
codesign -f -s "iPhone Distribution: XXX(证书名称)" --entitlements en(Profile配置文件) XXX.app(签名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 指令
## 通过yololib工具实现注入动态库
yololib "MachO文件路径" "需要注入的动态库路径"
注入成功后再对所有Framework签名,最后对app重签名,然后生成ipa文件。
3.4 关于重签名的思考
iOS重签名实现,可以发现用于签名的私钥资源(包括.cer证书和Provisioning Profile配置)和实际签名的app包是没有强关联关系的,这也就带来了两方面的问题。
- .cer证书和Provisioning Profile配置被用于其他App的分发签名,特别如果是In-House企业类型的证书,那是可以进行无限制分发的,而一旦苹果检测到这种违规签名的行为,轻则撤销证书,重则注销企业开发者账号!这也就是为什么一定要严格把控 p12、Provisioning Profile 文件外发的原因。
- 自有App被注入代码后重签名,比如应用多开、添加插件、恶意抓包等等,对于这一类的防护除了对Bundle ID进行检查,以及对App动态库增加白名单检索外好像也没有更好的办法。当然这已经涉及到逆向防护的方向了,本人对此还未深入了解,有兴趣的同学可以一起参与探讨。
全文完!
这里整理了一份用于重签名的脚本 CJCodeSign,想了解更多关于签名指令的内容可点击查看详情。
作者简介:lele8446,iOS开发深耕者,爱好分享、深⼊探讨有温度的内容,GitHub 地址。