导语:iOS 签名类型有 Development、AD-Hoc、In-House、App Store,而打包过程中又涉及到各种证书、Provision Profile、entitlements、CertificateSigningRequest、p12、AppID......各种概念一大堆,本文将从打包签名的原理说起,并梳理完全签名的整体流程,最后讲解重签名的实现以及签名机制中有哪些是需要注意防护的要点。
为了保证 App 的分发平台是可控的,以及保证所有安装到 iOS 设备上的 App 都是经过苹果官方允许的,苹果建立了 iOS 签名打包机制。要了解 iOS 签名机制的实现,我们首先从签名机制的原理说起。
网络数据的传输可以使用对称加密以及不对称加密的方式进行安全防护,对称加密是指数据发送者(A)和接收者(B)双方进行加解密的密钥是一致的,但这样会增加密钥自身分发的不安全性:比如要如何保证密钥在传递过程中不被泄露。
而不对称加密则由 A、B 持有一对公私钥进行加解密,公私钥钥匙对是成对出现的。对于一个私钥,有且只有一个与其对应的公钥,私钥保密、公钥公开,但是不能通过公钥推导出私钥,使用私钥加密的文件可用公钥解密,反过来公钥加密的文件也只能用私钥进行解密。加密过程如下:
这里主要解决了两个问题,一个是加密数据大小的问题,另一个是如何验证公钥的有效性。
前面已经讲到,iOS 打包安装的过程中会对 ipa 包进行加解密验证。然而 ipa 安装包大小动辄就有十几 M,大的有好几 G,那如果对这么大的数据量进行加解密,肯定效率是非常低的。而信息摘要则是解决了加密数据过大的问题,其原理是对信息内容通过一个很难被逆向推导的公式计算得到一段哈希数值,它具有以下特点:
使用信息摘要技术在数据加密传输时,发送方先对文件内容使用哈希算法进行信息摘要计算,再对摘要内容进行加密,之后将文件内容以及摘要内容(已加密)发送出去。
接收方收到数据后,先解密得到摘要内容,再依据相同的哈希算法对文件内容进行信息摘要计算,最后匹配接收到的哈希值与计算得到的哈希值是否一致,如果一致那就说明传输过程是安全的。
这样也就避免了对整体原数据加解密的计算过程,从而提高了验证效率。
不对称加密中的公钥是公开的,谁都可以得到,这样也就存在了不安全性。比如主动攻击者 C 冒充数据发送者 A,将自己伪装后的公钥分发给数据接收者 B,从而达到监听 A、B 之间通信的目的,又或者是对 A、B 之间的通信数据进行注入攻击。
那为了保证获取公钥的安全性,这里引入 CA 认证(Certificate Authority)。CA 是证明公钥合法性的权威机构(Apple 就属于 CA 认证机构),它为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。用户使用 CA 的公钥对数字证书上的签名进行验证,如果验证通过,也就认为证书内包含的公钥是有效的。
CA 认证确保了用户公钥使用过程中的安全性,iOS 打包需要向苹果开发者中心上传.certSigningRequest
文件,然后配置得到各种.cer
证书,这些流程中便包括了开发者向 Apple CA 认证中心注册公钥的过程。
.certSigningRequest
文件,这个过程会从 Mac 终端生成一对钥匙对,私钥存储在 Mac 中,公钥则包含在.certSigningRequest
中。再将.certSigningRequest
文件上传到 Apple 后台即苹果开发者中心,则可以对应生成开发证书或者发布证书(.cer
文件)。.p12
将私钥导出给其他团队成员使用。Identifiers
是 iOS 设备安装应用时用来识别不同 App 的唯一标识,点击创建 App IDs,同时勾选 app 所包含的权限:APNs、HealthKit、iCloud 等。.cer
文件只是声明了证书的类型,比如 Apple Development、Apple Distribution、APNs 推送等等,而至于使用什么证书打包、AppID 是什么、打包的 App 包含了哪些功能、可以在哪些设备上安装,则是通过Provisioning Profile
描述文件(.mobileprovision
后缀)来说明的,苹果后台将所有这些信息组合后再使用 Apple 私钥进行签名,最后生成Provisioning Profile
描述文件:发布 App 至 AppStore 之前需要经过苹果后台审核,审核通过苹果后台会用 Apple 私钥对 App 数据进行加密签名生成 ipa 包;用户从 AppStore 下载 App 后,使用设备内置的 Apple 公钥解密验证,验证通过安装成功。由于 AppStore 分发的过程中上传审核、下载安装的整个过程都处在苹果的生态链内,所以只需要一次验证就能保证安全性。
从 AppStore 下载安装 App 只需要一次数字签名就足以保证安全性,但除了这种途径苹果还有其他的安装方式:
那这些安装 App 的过程中苹果又是怎样保证流程安全性的呢?答案就是双重签名机制,苹果使用前面讲到的 Mac 本地钥匙对以及 Apple 后台钥匙对进行多次数字签名,从而保证整体流程的可控。
钥匙串访问
在本地生成一对公私钥钥匙对,下面默认为公钥 L、私钥 L(L:Local)。Mach-O
可执行文件会把签名直接写入这个文件中,其他资源文件则会保存在_CodeSignature
目录下。你可以将打包生成的.ipa
文件另存为.zip
,解压后对Payload
文件夹中的.app
文件右键、显示包内容,就可以看到签名数据。Plugins、Watch、Frameworks
文件夹),每一个都会单独进行一次签名,并生成各自的Mach-O
可执行文件和_CodeSignature
。entitlements
一致等等。以上流程便是开发调试、AD-Hoc、In-House 等方式打包安装 App 的过程,区别只在于第⑤步中设备IDs
的匹配规则不一致。开发调试只安装当前联调的设备;AD-Hoc 允许安装到已在开发者账号下注册过的设备,且每年最多允许 100 台;In-House 无设备数量限制,常用于企业内部 App 的分发。
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
则是下载后主动安装的,大部分情况下都是由管理员创建下载好之后,导出分发给团队成员。
iOS 签名调用的是 codesign 指令,你也可以直接使用相关指令进行签名,下面是 codesign 的常用指令:
# MAC终端输入: codesign --helpcodesign --helpUsage: 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 entitlements.plist(Profile配置文件) XXX.app(签名app)
复制代码
.ipa
重命名为.zip
,然后右键解压,将会生成一个 Payload 文件夹,里面包含.app
文件。Provisioning Profile
文件重命名为 embedded.mobileprovision,并拷贝放到 Payload 文件夹中。同时右键.app
文件,显示包内容,将前面的 embedded.mobileprovision 文件再拷贝一份放到.app
文件夹中,替换掉原有的embedded.mobileprovision
。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>
复制代码
security find-identity -v -p codesigning
复制代码
.app
显示包内容,查看动态库和插件(Plugins、Watch、Frameworks
文件夹),如果是个人证书需要移除Plugins、Watch
文件夹,因为个人证书没法签名 Extention。如果存在Frameworks
,则执行签名指令,有多个的话则每一个 Frameworks 都要重签一次。 codesign -fs "签名证书名称" "Frameworks/xxx.framework(动态库路径)"
复制代码
codesign -f -s "iPhone Distribution: XXX(证书名称)" --entitlements entitlements.plist(Profile配置文件) XXX.app(签名app)
复制代码
.ipa
,这样重签后的 ipa 便已生成了,你可以通过 iTunes、iTools 或其他途径安装到 iOS 设备上。TARTETS — Framework & Library — Framework
,然后在 framework 中添加自定义代码,一般都是使用 Runtime 来注入附加功能。最后选择 framework 要支持的架构,编译后便得到了最终动态库。Framework
文件夹(没有则新建)中。然而此时动态库与 app 还没建立关联关系,动态库需要注入MachO
中才能生效。注入使用 yololib工具,下载 yololib 并编译,将生产的命令复制到/usr/local/bin
或$PATH
中的其他路径,便可以在终端使用yololib
指令 ## 通过yololib工具实现注入动态库 yololib "MachO文件路径" "需要注入的动态库路径"
复制代码
注入成功后再对所有 Framework 签名,最后对 app 重签名,然后生成 ipa 文件。
这里整理了一份用于重签名的脚本 CJCodeSign,想了解更多关于签名指令的内容可点击查看详情。
iOS 重签名实现,可以发现用于签名的私钥资源(包括.cer
证书和Provisioning Profile
配置)和实际签名的 app 包是没有强关联关系的,这也就带来了两方面的问题。
.cer
证书和Provisioning Profile
配置被用于其他 App 的分发签名,特别如果是 In-House 企业类型的证书,那是可以进行无限制分发的,而一旦苹果检测到这种违规签名的行为,轻则撤销证书,重则注销企业开发者账号!这也就是为什么一定要严格把控 p12
、Provisioning Profile
文件外发的原因。全文完!
最后再附上重签名脚本地址: CJCodeSign
作者GitHub地址。
领取专属 10元无门槛券
私享最新 技术干货