欢迎访问我的博客原文
不少果粉对 Apple 钟情,与它的纯净、安全有很大关系,我们发现在苹果的设备上下载应用时,不会出现触发下载一系列垃圾软件的情况,而且用户可以明确 App 的来源——通过官方商店 AppStore 购买、企业证书安装还是 TestFlight 下载。为了防止盗版软禁、病毒入侵、静默安装以及屏蔽其它不可控因素,并确保每一个安装到 iOS 设备上的应用都是被官方允许的,苹果设定了一套应用签名机制。
数字签名
数字签名,又称公钥数字签名,是只有信息的发送者才能产生的别人无法伪造的一段数字串,发送者对要发送的数据打上签名标记,表示这份经过认证,未被篡改的。
数据传输
下面模拟一下数据传输的过程:
假如发送方直接将原始数据明文传输给接收方时,数据非常不安全,极易被篡改;
为了提升安全性并同时简化明文,可以对数据进行哈希算法处理,得到原始数据的摘要,然后将摘要发送给接收方。但假如哈希算法被泄漏,依然存在数据被篡改的风险;
引入非对称加密算法,对一份数据,用哈希算法计算出摘要后,再用 RSA 的私钥加密摘要,得到原始数据的数字签名,发送方将数字签名与原始数据一起发送给接收方。
我们将原始数据进行哈希加密、非对称加密后的数据称为数字签名。
接收方拿到数据后,需要进行签名验证,来确保数据传输过程中,未被篡改。
数字签名验证
签名验证的具体步骤如下:
接收方拿到数据后,通过同样的哈希加密处理原始数据,得到哈希值(摘要);
再利用非对称将数字签名中的校验哈希值(摘要)解密出来;
最后对比两个哈希值是否一致,判断出数据是否被篡改。
用一张图还原数字签名的完整过程:
再来看看如何利用数字签名保证每个安装到 iOS 上的 App 都被苹果认证允许。
代码签名
代码签名就是对可执行文件或脚本进行数字签名,用来确认软件在签名后未被修改或损坏的措施。它的原理和数字签名类似,只不过把签名的不是数据,而是代码。
简单的代码签名
假如 App 是只能从 App Store 上下载,那么它的验证方式就比较简单了。
由苹果官方生成一对公私钥,在 iOS 系统中内置一个公钥,私钥由苹果后台保存。
我们把 App 上传到 App Store 时,苹果后台用私钥对 App 数据进行签名,iOS 系统下载这个 App 后,用公钥验证这个签名,如果签名正确则这个 App 肯定是由苹果后台认证的,并且没有被修改或损坏。
但 iOS 设备安装 App 并不只有 App Store 这一个渠道,比如开发者的真机调试、TestFlight 内测、In-House 企业证书分发等,此时简单的代码签名就无法满足对 App 的完全验证了。
iOS 代码签名的复杂度需要相应增加,于是双层代码签名(双重签名)产生了。
双层代码签名
“双层”意在用两对公私钥做加密验证,它们分别是 Mac 本地的一对和 Apple 服务提供的一对。
双层代码签名的存在是为了满足:
- App 需要经过苹果允许才能安装;
- 在 Apple 后台中注册过的设备才能安装,比如在 TestFlight 内测、真机调试模式下;
- 限制签名只能对应唯一的 App。
为了猜测完整的签名流程,我们可以解压一个 ipa 文件,在 Payload 目录中有一个 embedded.mobileprovision
,我们称之为描述文件,它对应的是 Apple 后台生成 Provisioning Profile
(简称 PP)文件。文件中包括:
- 证书(公钥、签名)
- App ID
- Entitlements(权限)
- 注册设备列表
- 其它关乎 App 能否正常启动的所有信息
所以我们猜测签名的大概流程是这样的:
在开发设备 Mac 上本地生成一对公私钥。
Apple 有一对公私钥,Apple 私钥在 Apple 后台,Apple 公钥在每台 iOS 设备上。
把 Mac 公钥上传到 Apple 后台,用 Apple 私钥签名 Mac 公钥,可以得到一份 Mac 公钥和签名的组合数据,我们把这份数据称为证书。
在 Apple 后台申请 App ID,配置好的 UDID(注册设备) 列表以及 App 申请的权限(Entitlements),再加上步骤3中的证书,组合起来的数据用 Apple 私钥进行签名,把数据和签名一起组成 PP 文件,下载到本地的开发设备 Mac 上。
当我们编译工程时,Mac 私钥会对 App 进行签名,同时把步骤4得到的 PP 文件打包进去,文件名为
embedded.mobileprovision
,准备将 App 安装到手机上。安装时,iOS 系统取得证书,通过系统内置的 Apple 公钥,去验证证书里的签名是否正确。
继续用 Apple 公钥验证描述文件是否正确。
用 Mac 公钥验证 App 签名是否被篡改。
上面的步骤对应到实际操作和概念是这样的:
第 1 步:Mac 上依次打开“钥匙串访问 → 证书助理 → 从证书颁发机构请求证书...”,做了这一步,就会在本地生成了一对公私钥,导出的 CSR 文件(CertificateSigningRequest.certSigningRequest
)就是 Mac 公钥,Mac 私钥也是存储在本地,具体是什么文件看第 3 步。
第 2 步:每台 iOS 设备中都已经有了 Apple 公钥,至于 Apple 私钥是什么,看第 3 步。
第 3 步:在 Apple 后台的 iOS Certificates 模块,通过上传本地导出的 CSR 文件,生成 .cer
证书文件,也就是 Apple 私钥。将 .cer
证书下载到本地,安装证书,在钥匙串中找到证书,就可以导出 Mac 私钥,也就是一个 .p12
文件。它和第 1 步中导出的 Mac 公钥是对应的,钥匙串会把这两个证书关联起来。用.cer
证书去签名 CSR 文件,拿到含有签名的证书。
第 4 步:在 Apple 后台配置 App ID、Entitlements、Devices 等,然后下载 PP 文件。
第 5 步:编译 App 时,Xcode 会通过第 3 步下载回来的证书(存着 Mac 公钥),在本地找到对应的 Mac 私钥,然后用 Mac 私钥去签名 App,同时打包,安装包中包含 PP 文件,在 ipa 中的文件名是 embedded.mobileprovision
。这里 App 的签名数据被分为两部分,Mach-O 可执行文件会把签名直接写入描述文件里,而资源文件则会保存在 _CodeSignature
目录下,这时准备安装 App。
第 6 步:使用 Apple 公钥验证描述文件签名,对应第 4 步,签名通过,说明证书可用,进入下一步。
第 7 步:使用 Apple 公钥验证证书签名,对应第 3 步,签名通过,说明 Mac 公钥合法,进入下一步。
第 8 步:使用 Mac 公钥验证 App 签名,对应第 4 步,上述验证均通过后,还需要将描述文件中的内容与 App 本身的信息做验证对比,比如验证设备 ID 是否在 UDID 列表上,App ID 是否相同,权限开关是否与 Entitlements 一致,都验证通过,就可以开始安装 App。
前面说了,双层代码签名是针对开发测试包、In-House 企业签名、Ad-Hoc 包为例的签名和验证的流程,只是企业签名不限制安装的设备数,因此描述文件中不会有设备列表,而是一条
记录。
而从 App Store 上下载的安装包,里面是没有描述文件的,但上架之前还是要配置证书、PP 文件,因为 App ID 和权限的检验还是需要做的。但 App 上传到 AppStore 以后就跟 PP 文件没有关系了,所以我们可以理解为 App Store 上包的签名验证采用就是前面说的最简单的签名方式,Apple 后台直接用私钥签名 App 就可以了。