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 持有一对公私钥进行加解密,公私钥钥匙对是成对出现的。对于一个私钥,有且只有一个与其对应的公钥,私钥保密、公钥公开,但是不能通过公钥推导出私钥,使用私钥加密的文件可用公钥解密,反过来公钥加密的文件也只能用私钥进行解密。加密过程如下:

  1. 发送方(A)首先生成一对公私钥钥匙对,私钥自己保管,公钥则任意分发出去(每台 iOS 设备终端其实已经包含 Apple 的公钥)。

  2. 发送数据时,发送方使用私钥对原数据加密成密文传输(加密打包 ipa);

  3. 接收方(B)收到密文后,使用之前已经获取到的公钥进行解密得到数据内容(iOS 设备验证安装 ipa)。

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

  • .cer 文件:Apple 后台使用 Apple 私钥对 Mac 公钥进行签名后生成的证书。

  • .p12 文件:Mac 本地生成的钥匙对私钥。由于私钥是本地私有的,但你可以使用.p12将私钥导出给其他团队成员使用。

  • IdentifiersIdentifiers是 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.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 进行签名。

    5. 这些签名数据包含两部分:Mach-O可执行文件会把签名直接写入这个文件中,其他资源文件则会保存在_CodeSignature目录下。你可以将打包生成的.ipa文件另存为.zip,解压后对Payload文件夹中的.app文件右键、显示包内容,就可以看到签名数据。

    6. 另外签名过程中对于 App 内包含的动态库以及插件(Plugins、Watch、Frameworks文件夹),每一个都会单独进行一次签名,并生成各自的Mach-O可执行文件和_CodeSignature

    7. 签名数据指代码内容、App 包含的所有资源文件,只要其中有任何改动,都必须重新签名才有效。

    8. 打包的过程中会将描述文件 Provisioning Profile 命名为 embedded.mobileprovision 放入到打包 app 中。

    9. 安装/启动,iOS 设备使用内置的公钥 A 验证 embedded.mobileprovision 是否有效(设备是否在允许安装列表内),同时再次验证里面包含的**.cer 证书签名是否有效(证书过期与否)并取出公钥 L**。

    10. embedded.mobileprovision 验证通过,就使用公钥 L 解密验证 app 签名信息:AppID 是否对应、权限开关是否跟 app 里的entitlements一致等等。

    11. 所有验证通过,安装/启动完成。

    # 签名指令
    codesign -f -s "iPhone Distribution: XXX(证书名称)" --entitlements entitlements.plist(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

    • entitlements.plist 是由签名证书对应的 Profile 配置导出的签名文件,它与前面截图 Xcode 签名日志中的XXX.xcent文件的作用相同。终端 cd 到 Payload 文件夹路径,执行指令

      # cd xxx/Payload,然后执行下面指令
      security cms -D -i embedded.mobileprovision
    
    

    将会打印出 Profile 配置的内容,找到<key>Entitlements</key>,然后把<key>Entitlements</key>下面<dict>...</dict>的内容拷贝到新建的entitlements.plist文件中(可以通过 Xcode 生成 plist 文件,选 Property List 类型),最后将entitlements.plist文件放到 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.apple.developer.team-identifier</key>
          <string>xxx</string>
        </dict>
    
    

    • 签名证书名称可以在安装证书后从钥匙串中心查看

    • 对需要重签名的.app 右键显示包内容,然后将动态库拷贝到Framework文件夹(没有则新建)中。然而此时动态库与 app 还没建立关联关系,动态库需要注入MachO中才能生效。注入使用 yololib工具,下载 yololib 并编译,将生产的命令复制到/usr/local/bin$PATH中的其他路径,便可以在终端使用yololib指令

      ## 通过yololib工具实现注入动态库
      yololib "MachO文件路径" "需要注入的动态库路径"
    
    

    注入成功后再对所有 Framework 签名,最后对 app 重签名,然后生成 ipa 文件。

    这里整理了一份用于重签名的脚本 CJCodeSign,想了解更多关于签名指令的内容可点击查看详情。

    3.4 关于重签名的思考

    iOS 重签名实现,可以发现用于签名的私钥资源(包括.cer证书和Provisioning Profile配置)和实际签名的 app 包是没有强关联关系的,这也就带来了两方面的问题。

    1. .cer证书和Provisioning Profile配置被用于其他 App 的分发签名,特别如果是 In-House 企业类型的证书,那是可以进行无限制分发的,而一旦苹果检测到这种违规签名的行为,轻则撤销证书,重则注销企业开发者账号!这也就是为什么一定要严格把控 p12Provisioning Profile 文件外发的原因。

    2. 自有 App 被注入代码后重签名,比如应用多开、添加插件、恶意抓包等等,对于这一类的防护除了对 Bundle ID 进行检查,以及对 App 动态库增加白名单检索外好像也没有更好的办法。当然这已经涉及到逆向防护的方向了,本人对此还未深入了解,有兴趣的同学可以一起参与探讨。

    全文完

    最后再附上重签名脚本地址: CJCodeSign

    作者GitHub地址。

  • 本文文字及图片出自 InfoQ

    本文文字及图片出自

    余下全文(1/3)
    分享这篇文章:

    请关注我们:

    共有 1 条讨论

    1. admin  这篇文章, 并对这篇文章的反应是俺的神呀赞一个

    发表回复

    您的电子邮箱地址不会被公开。 必填项已用 * 标注