如何理解 iOS 的签名证书机制

5,018 阅读9分钟
原文链接: blog.gocy.tech

从接触 iOS 开发的第一天起,就被 Xcode 的证书、签名的一堆验证流程弄得晕头转向。从一开始的弹窗 Fix issue,到 Xcode 8 之后出现了 Automatically manage signing,苹果正努力让整个签名的过程变得简单透明。但这个机制出现的原因、背后的机理是什么呢?本文将记录我就这一问题的学习心得。

准备工作

要理解 iOS 中的签名和证书机制,首先我们要理解什么是签名(Signature),什么是证书(Certificate)。

签名

在数据传输的过程中,我们最关心的就是两件事:可靠性和数据完整性。我们可不想打开身份不明的开发者的程序,或是打开已经损坏/被修改过的程序。为校验数据包的完整性,数据发送端通常会利用 MD5 算法 来将需要发送的信息进行“摘要”,而后发送端利用私钥将摘要加密,得到签名,连同数据包一起发送给接受方,接受方收到数据包和签名后,接受方利用公钥解密签名,拿到发送方的 MD5 数据,再对数据包进行 MD5 操作并与发送方 MD5 进行对比,以验证数据完整性。上述的过程,摘要的加、解密步骤,验证了数据发送方的身份,而两次 MD5 操作验证了数据完整性。如果你对非对称加密不太熟悉,我推荐你看看 这篇文章

一般情况下仅对 md5 内容进行加密,是为了节省加密、解密的时间。

证书

上述签名过程看起来已经足够安全了,接受方通过发送方公钥对签名进行解密,就能够验证数据包发送者的身份。但这都是建立在我们手中的公钥是正确的前提下的,一旦我们的公钥被替换,我们便无法解开对应发送方的签名,但更危险的是,伪造公钥的第三方,可以发送数据给我们,而我们拿着“假”的公钥,能正确解开其签名,从而误以为该数据源于我们所信任的发送方。那么如何确保公钥的安全性呢?一个很自然的想法就是为公钥进行加密。
通常的做法是,我们找到一个可信任的数字证书认证机构(Certificate Authrotiy,简称 CA),将我们自己的公钥交给 CA,随后该机构运用上述签名类似流程,用 CA 私钥加密我们的公钥,然后暴露一个 CA 公钥供使用者验解密。这个被 CA 私钥所加密的我们自己的密钥(当然还有一些像有效期、用户信息等其它内容)一般被称为证书。但这里也引入了另一个问题:解密证书所需要的公钥,同样存在被冒充的风险,如何规避这其中的风险呢?这里我并没有查到详细的资料,我想,这里公钥的正确性,便是由这些具有公信力的 CA 所保证的,而平时我们自己做业务,在网络安全方面,是难以达到这样的安全标准的。

小结

有了上面的储备知识,我们知道,想要在网络上安全的传输数据,我们一般需要两对密钥:自己的业务服务器公私钥以及 CA 的公私钥。接下来让我们看看苹果是如何执行这样的思想的。

iOS 的签名证书机制

接下来我们看看 iOS 上的签名机制,下文只讨论 AppStore 以及本地 build 这两种情况(本文编写时,Xcode 版本为 8.3 (8E162),若是更早的版本,macOS 上证书的生成与导入可能需要手动完成)。

为什么需要

相信大家入门程序员的时候,大多都是在 VC 或 VS 上打 C/C++ 入门的,那时候一切都很简单,code-build-link-run,并没有什么认证过程,而由于苹果对其生态圈管理严格,任何在 iOS 上跑的程序都需要经过苹果的“同意”。所以,不论是 AppStore,还是 Xcode 本地 build 的项目,苹果都需要验证这个应用的身份。

AppStore

从 AppStore 下载的应用验证流程非常简单,Apple 用私钥签名 App 后,iOS 设备下载签名和 App,利用 Apple 公钥验证签名并安装。由于 iOS 设备内置 Apple 公钥,而数据包直接由苹果服务器下载,因此此处的安全是有保障的,同时,签名验证能通过,证明该安装包是从苹果服务器下载的,也就满足了苹果对 App 安装的控制。

Xcode Build

从 AppStore 分发应用的过程是非常简单的,但在日常开发中,上述的验证过程是不能被接受的,我们不可能每次 build 都将应用打包上传,经过苹果的加密后再下载运行。于是苹果想出了这么一套认证体系。
从 AppStore 下载应用时,iOS 设备会用 Apple 公钥验证应用安装包,以确保该应用来源于 Apple 服务器。而在本地调试模式下,设备会把对 App 的验证,改为对开发者的验证:

此处的开发者账号,就是在 Apple Developer Center 中所申请的开发者账号,一旦信任之后,每次 build 的时候,iOS 设备就将验证该开发者身份,而不是验证你 build 的 App。如此一来,便省去了应用包上传、下载的过程。我们知道,验证 App 是通过签名机制,来确定安装包来源的,而此处的验证开发者身份,又是怎样的机制呢?

验证开发者

首先,iOS 设备需要验证你的开发者身份,这是基于 macOS 的一种证书策略,如果打开钥匙串访问,选中证书,我们可以看到这样的一堆东西:

这里你可以看到你的企业证书、个人开发者证书等等,我这里还有 Charles 用来做网络代理的证书。为了更好的说明这一工作机制,我用我的 gmail 邮箱重新申请了一个开发者账号。当你在 Xcode 添加自己的账号后,并在项目中将 Team 选择为自己的开发者账号后,钥匙串中就多出了这么一个东西:

我们可以看到,证书中存储着一个“专用密钥”,这个专用密钥,和前面 AppStore 分发过程中的 Apple 密钥作用类似,是用来给 App 安装包进行签名的。这份密钥在生成的时候,Xcode 将密钥的公钥部分上传至 Apple 服务器,由 Apple 进行签名生成证书,最后返回到本地,而私钥,则会在 Xcode 本地构建安装时,用来为 App 安装包签名。最后,Xcode 会将签名后的安装包和本地证书打包都放到 iOS 设备中,iOS 设备接着使用 Apple 公钥来解密证书,拿到本地公钥,接着用本地公钥去解密 App 。

绕了这么一大圈,无非是为了验证两件事:

  1. iOS 设备用 Apple 公钥解密证书,所拿到的本地证书,必定是经过 Apple 服务器签名的,也就是说,这里保证了开发者是经过苹果认证的。
  2. 上一步拿到的公钥,用于解密 App 签名,若解密成功且验证通过,证明该 App 确实是由该开发者构建的,也就保证了 App 的安全性。

或许看看流程图,能更清楚:

上述的流程唯一问题是,这个密钥是在本地生成的,如果更换了电脑,本地的密钥对就会改变。这时候,你需要将旧 mac 上的证书导出,并安装到新的电脑上。

一一对应

我们能在 mac 的钥匙串访问中找到本地的密钥证书,但其它的东西似乎就不是那么地可见了,对于一般的开发者来说,我们更熟悉的概念应该是 Provisioning Profile,Xcode 8.3 提供了很友好的可视化界面来让我们查看 Provisioning Profile 里面的东西。


我们可以看到,Provisioning Profile 中包含了开发组名、组内设备、应用 Capabilities 配置等等,这些都是决定应用行为的一些信息。我们还注意到,Provisioning Profile 中包含了一个 Certificates,这个证书就是我们在上面说的本地公钥证书。其实在 Xcode 构建安装 App 时,我们不是直接将本地证书装入 iOS 设备中,而是将 Provisioninng Profile 装入设备中。如果我们开启了 Automatically manage signing,Provisioning Profile 的生成和下载都是由 Xcode 自动完成的,但其实,Provisioning Profile 的创建过程,和本地密钥证书的过程极其相似。如果你是付费开发者,或是加入了企业开发 team,你就能在 证书管理 看到所有的 Provisioning Profile,每个 Provisioning Profile 与 teamId 和 bundleId 唯一对应。类似的,你也需要将它下载到本地,只不过这一步很多时候 Xcode 替我们完成了。

Xcode 默认存储 Provisioning Profile 的文件夹

由于 Provisioning Profile 在 Apple 后台生成,很自然的,它也会被 Apple 私钥加密,而它的作用主要有两点:

  1. 校验 App 的各项参数(如 Capabilities,后台获取、通用链接等配置)是否与苹果后台定义的一致。
  2. 其中包含的 Certificates,也就是本地证书,用于校验开发者身份。

所以,我们可以将上一部分的图进行一些细化:

这里对身份验证的原理和上一部分一致,只是多了一层封装,用于校验项目信息。

总结

至此,我们大致了解了 AppStore 以及 Xcode 本地构建时,iOS 所使用的身份验证机制了。整套验证流程其实就是对非对称加密的封装和应用,只是苹果为验证项目信息,又不想与“证书”这一专有名词混淆,加入了一个 Provisioning Profile 的概念。如果你熟悉签名、证书的概念,那么其实理解这一套验证机制也就并不困难了。

参考资料

iOS App 签名的原理
数字签名是什么?
Public-key cryptography for non-geeks