对于基础的加密相关的概念可以看文章:# 加密、摘要、签名、CA证书、HMAC、CRC全面梳理
一、苹果证书签名机制
一句话:用非对称加密来验证身份,用摘要算法来确保完整性。
大体流程如下: 
三个关键角色和他们的密钥:
js
┌─────────────────────────────────────────────────────────┐
│ 1. 你的 Mac(开发者) │
│ 私钥 L ← 藏在钥匙串里,用来签名 App │
│ 公钥 L ← 放进 CSR 里,最终放进证书里 │
│ │
│ 2. 苹果后台(CA 角色) │
│ 私钥 A ← 苹果绝对保密的顶级私钥 │
│ 公钥 A ← 出厂内置在每台 iPhone/iPad 的系统里 │
│ │
│ 3. iPhone / iPad 用户设备 │
│ 公钥 A ← 系统内置,用来验证苹果签过名的东西 │
└─────────────────────────────────────────────────────────┘
下面我们再分步骤拆解,看看加密和签名具体是怎么运用的。
1. 生成证书请求文件(CSR):(你 Mac 本地)
这是整个流程的起点, 你在Mac的钥匙串里申请CSR时,会生成.certSigningRequest文件,系统会悄悄地做两件事:
js
你 Mac 本地操作:
┌──────────────────────────────────────────┐
│ 密钥串访问 → 证书助理 → 从证书颁发机构请求证书 │
│ │
│ 系统后台自动做: │
│ │
│ 1. 生成密钥对 │
│ ┌───────────┐ ┌───────────┐ │
│ │ 私钥 L │ │ 公钥 L │ │
│ │ (留在本地) │ │ (要发出去) │ │
│ └───────────┘ └─────┬─────┘ │
│ │ │
│ 2. 组装 CSR 文件 ▼ │
│ ┌────────────────────────────┐ │
│ │ CSR 内容: │ │
│ │ · 你的邮箱/名称 │ │
│ │ · 公钥 L │ │
│ │ · 用私钥 L 对上述内容签名 │ │
│ └────────────────────────────┘ │
│ │
│ CSR 证明了: │
│ "持有私钥 L 的人,申请让苹果认证公钥 L" │
└──────────────────────────────────────────┘
用到的技术:
非对称加密 ✓ (签名部分)
摘要算法 ✓ (签名前对内容做 SHA-256)
2. 苹果签发证书(苹果服务器):开发证书(.cer)
你把CSR文件上传到苹果开发者网站,苹果作为CA,会验证你的身份(开发者账号),然后用它的私钥A 给你的公钥L (以及其他信息)做一个签名认证,最后打包成一个.cer证书文件发给你。
这个证书证明了"公钥L是经过苹果官方认证的合法开发者公钥"。
js
你上传 CSR → 苹果服务器
┌──────────────────────────────────────────────────────┐
│ 苹果后台处理: │
│ │
│ 1. 验证 CSR 签名 │
│ 用 CSR 里的公钥 L,验证签名是否合法 │
│ → 合法说明你确实持有私钥 L │
│ │
│ 2. 验证开发者身份 │
│ 检查你的 Apple Developer 账号是否有效 │
│ │
│ 3. 签发证书 │
│ ┌─────────────────────────────────────┐ │
│ │ 开发证书 .cer 内容: │ │
│ │ │ │
│ │ · 你的 Team ID / 账号信息 │ │
│ │ · 公钥 L │ │
│ │ · 证书类型(开发/发布) │ │
│ │ · 有效期 │ │
│ │ · 苹果的签名(私钥A, 上述所有内容) │ │
│ └─────────────────────────────────────┘ │
│ │
│ 你下载 .cer 文件并安装到钥匙串 │
│ 此时钥匙串里的私钥 L 和证书正式配对 │
└──────────────────────────────────────────────────────┘
证书 .cer 里有什么:
你的信息 + 公钥L + 苹果签名(私钥A, 你的信息+公钥L)
用到的技术:
非对称加密 ✓ (苹果用私钥A签名证书)
摘要算法 ✓ (签名前 SHA-256)
3. 配置描述文件(.mobileprovision):一张临时的"权限通行证"
描述文件则整合了你申请的所有权限。它包含:
- 你的开发者证书(即包含公钥L)。
- App ID:指定这个签名能用于哪个App。
- 设备UDID列表:指定能在哪些设备上运行(仅开发/Ad-Hoc证书)。
- 授权信息(Entitlements):比如能否使用APNs推送、iCloud等。
它也经过苹果私钥A的完整签名,防止被篡改。
js
你在开发者网站配置 App ID、UDID → 苹果生成描述文件
┌─────────────────────────────────────────────────────┐
│ 描述文件 .mobileprovision 内容: │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ · App ID (com.example.yourapp) │ │
│ │ · 开发者证书链(包含你的 .cer) │ │
│ │ └── 你的证书里有公钥 L │ │
│ │ · 授权设备 UDID 列表(开发证书才有) │ │
│ │ · 权限 Entitlements(推送、iCloud 等) │ │
│ │ · 有效期 │ │
│ │ · 苹果签名(私钥A, 上述所有内容) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 描述文件也经过苹果签名,防止被篡改添加设备或权限 │
└─────────────────────────────────────────────────────┘
描述文件里有什么:
权限清单 + 你的证书(含公钥L) + 苹果签名(私钥A)
用到的技术:
非对称加密 ✓ (苹果用私钥A签名整个描述文件)
4.签名 App(Xcode 本地):给APP盖上防伪章
当你用Xcode打包App时,Xcode就会使用存储在钥匙串里的私钥L ,对你App包的每一个二进制文件和资源文件进行数字签名。具体的做法是:
js
Xcode 打包 App 时:
┌─────────────────────────────────────────────────────┐
│ Xcode 做的事情: │
│ │
│ 1. 从钥匙串取出 私钥 L 和证书 │
│ │
│ 2. 对 App 内每一个可执行文件和资源: │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 原始文件(二进制/图片/plist) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ SHA-256 哈希 → 摘要值 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 私钥 L 签名摘要 → 签名值 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 签名值写入 App 包 _CodeSignature │ │
│ └──────────────────────────────────────┘ │
│ │
│ 3. 把描述文件 .mobileprovision 也打包进 App │
│ │
│ App 包最终包含: │
│ · 原始代码/资源 │
│ · _CodeSignature(每个文件的签名) │
│ · embedded.mobileprovision(描述文件) │
└─────────────────────────────────────────────────────┘
用到的技术:
摘要算法 ✓ (SHA-256 哈希每个文件)
非对称加密 ✓ (私钥 L 对摘要签名)
5.iOS 设备验证(安装/启动时):"双锁校验"
当App安装到iOS设备上时,系统会严格地过两道关:
-
第一道锁:验证描述文件和证书 iOS系统用内置的公钥A ,去验证
.mobileprovision描述文件的签名,确认权限未经篡改。然后,从中取出开发者的证书,再次用公钥A 验证证书签名,从而确认"公钥L"是苹果官方认证过的合法开发者身份。 -
第二道锁:验证App包体完整性 系统用刚刚验证过的合法公钥L ,去解密App包内的数字签名,得到一个"原始摘要"。然后,系统用相同的摘要算法重新计算App文件内容,得到一个"新摘要"。如果"原始摘要"和"新摘要"完全一致,则证明这个App自开发者签名后没有被任何第三方篡改或注入病毒过。
关于这套苹果签名体系,还有几个关键细节值得注意:
-
私钥是核心资产 :
.p12文件就是私钥L的备份(可设置密码保护),它必须被妥善保管,是代码签名能力的核心。一旦泄露,他人就能以你的身份签名App。 -
双层签名的必要性 :为什么需要苹果的私钥A 和开发者的私钥L双层签名?单纯用私钥L签名,iOS系统无法分辨这个签名来自张三还是李四。引入苹果私钥A的"认证链"后,苹果就完成了对开发者身份的终极背书,从而形成一个完整的信任闭环。
所以你看到的"证书请求文件 -> 开发证书 -> 描述文件 "这一整套流程,本质上就是苹果用自己的私钥A ,为你本地的公钥L 做信用背书,最终让你的私钥L能够有权在App上完成数字签名的完整逻辑。
js
用户下载/安装/启动 App 时,iOS 系统自动执行:
┌──────────────────────────────────────────────────────┐
│ │
│ 🔓 第一把锁:验证描述文件和证书 │
│ ┌────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 取出 App 里的 .mobileprovision │ │
│ │ │ │
│ │ 2. 用系统内置的公钥 A 验证苹果签名 │ │
│ │ 签名有效? │ │
│ │ ├── YES → 描述文件未被篡改 ✓ │ │
│ │ └── NO → ❌ 拒绝安装/启动 │ │
│ │ │ │
│ │ 3. 检查描述文件内容: │ │
│ │ · App ID 是否匹配? │ │
│ │ · 设备 UDID 在不在列表里? │ │
│ │ · 证书有没有过期? │ │
│ │ · 权限 Entitlements 是否合法? │ │
│ │ │ │
│ │ 4. 从描述文件里提取开发者证书 │ │
│ │ 用公钥 A 验证证书的苹果签名 │ │
│ │ 签名有效 → 公钥 L 是苹果认证过的 ✓ │ │
│ │ │ │
│ └────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 🔓 第二把锁:验证 App 代码完整性 │
│ ┌────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 从已验证的证书中提取 公钥 L │ │
│ │ │ │
│ │ 2. 对每个被签名的文件: │ │
│ │ │ │
│ │ 当前位置文件 ──SHA-256──► 新摘要 │ │
│ │ │ │
│ │ _CodeSignature 里的签名值 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 用公钥 L 验证签名 → 得到原始摘要 │ │
│ │ │ │
│ │ 新摘要 == 原始摘要 ? │ │
│ │ ├── YES → 文件未被篡改 ✓ │ │
│ │ └── NO → ❌ 拒绝启动(代码被修改过) │ │
│ │ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ 两把锁全通过 → App 正常启动 ✅ │
│ 任意一把失败 → App 闪退/拒绝安装 ❌ │
└──────────────────────────────────────────────────────┘
用到的技术:
非对称加密 ✓ (公钥A验苹果签名 + 公钥L验代码签名)
摘要算法 ✓ (SHA-256 校验文件完整性)
6. P12 文件的作用机制
一句话:P12 就是"私钥的搬家箱",让你能把私钥安全地从一台 Mac 搬到另一台 Mac,或者备份到安全的地方。
js
┌──────────────────────────────────────────┐
│ 私钥 L 极其重要 │
│ │
│ · 没有它 → 无法签名 App │
│ · 丢了它 → 永远无法更新这个 App │
│ · 别人拿到 → 可以冒充你签名 │
│ │
│ 问题: │
│ · 换电脑了怎么办? │
│ · 团队成员怎么共享同一个开发者证书? │
│ · 怎么备份? │
│ │
│ 答案:P12 文件 │
└──────────────────────────────────────────┘
P12 文件的内部结构
js
┌──────────────────────────────────────────────┐
│ P12 文件内部(PKCS#12 标准) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 1. 开发者证书 (.cer) │ │
│ │ · 你的信息 │ │
│ │ · 公钥 L │ │
│ │ · 苹果签名(私钥A) │ │
│ │ ← 这部分本来就是公开的 │ │
│ ├────────────────────────────────────┤ │
│ │ 2. 私钥 L (核心机密) │ │
│ │ · 256 位椭圆曲线私钥 │ │
│ │ · 非常敏感! │ │
│ │ ← 关键保护对象 │ │
│ ├────────────────────────────────────┤ │
│ │ 3. 可选:中间 CA 证书链 │ │
│ │ · Apple Worldwide Developer │ │
│ └────────────────────────────────────┘ │
│ │
│ 🔐 整个文件用 密码 + 对称加密 保护 │
│ │
│ 导出时你设置的密码: │
│ 123456 ──► 密钥派生 ──► AES 密钥 │
│ │ │
│ ▼ │
│ 整个 P12 内容被 AES 加密 │
│ 变成 .p12 二进制文件 │
└──────────────────────────────────────────────┘
P12 用了对称加密:
js
┌──────────────────────────────────────────────────────┐
│ │
│ P12 保护私钥的机制(对称加密的出场时刻): │
│ │
│ 你设置的密码(比如 "MyPassword123") │
│ │ │
│ ▼ │
│ PBKDF2 密钥派生函数(加盐 + 多轮哈希) │
│ │ │
│ ▼ │
│ 派生出一把 AES 对称密钥 │
│ │ │
│ ▼ │
│ 用 AES 加密整个 P12 容器 │
│ (里面的证书 + 私钥 + 证书链) │
│ │ │
│ ▼ │
│ .p12 文件 = 一堆加密后的二进制数据 │
│ │
│ 没有密码 → 拿不到 AES 密钥 → 解不开 P12 │
│ 有密码 → 派生出 AES 密钥 → 解开拿到私钥 L │
│ │
└──────────────────────────────────────────────────────┘
导入 P12 ------ 另一台 Mac
js
把 P12 文件发给另一台 Mac(或同一台重装系统后):
┌──────────────────────────────────────────────┐
│ 第二台 Mac 导入 P12 │
│ │
│ 1. 双击 .p12 文件 │
│ │
│ 2. 钥匙串弹出密码框: │
│ "请输入导出时设置的密码" │
│ │ │
│ ▼ │
│ 3. 你输入 "MyPassword123" │
│ │ │
│ ▼ │
│ 4. 系统用 PBKDF2 派生 AES 密钥 │
│ │ │
│ ▼ │
│ 5. 用 AES 密钥解密 P12 文件 │
│ │ │
│ ▼ │
│ 6. 解开后拿到: │
│ · 证书(公钥 L + 苹果签名) │
│ · 私钥 L │
│ · 证书链 │
│ │ │
│ ▼ │
│ 7. 安装到这台 Mac 的钥匙串里 │
│ │
│ 🔑 钥匙串 - 登录 │
│ ┌──────────────────────────────────────┐ │
│ │ 📜 iPhone Developer: 张三 │ │
│ │ ├── 公钥 L │ │
│ │ └── 私钥 L ← 成功搬过来了! │ │
│ └──────────────────────────────────────┘ │
│ │
│ 现在这台 Mac 也能签名 App 了 ✅ │
└──────────────────────────────────────────────┘
P12 的安全要点
js
┌────────────────────────────────────────────────────┐
│ │
│ ✅ 安全的地方: │
│ · P12 文件本身用 AES 加密,算法安全 │
│ · 密码用 PBKDF2 派生密钥,抗暴力破解 │
│ · 即使 P12 被截获,没密码也打不开 │
│ │
│ ⚠️ 风险点: │
│ · 密码太弱(123456)→ 暴力破解风险 │
│ · P12 + 密码通过同一渠道传输 → 一起泄露就完了 │
│ · P12 导入后,私钥又从 P12 解出来到新钥匙串 │
│ 此时 P12 本身的密码保护就没用了 │
│ 新的安全边界变成"新 Mac 的钥匙串" │
│ │
│ 💡 最佳实践: │
│ · 强密码(16位以上随机) │
│ · P12 和密码分开发送(如 P12 发邮件,密码打电话说) │
│ · 导入后 P12 文件可以删除(私钥已在钥匙串里) │
│ · 或者用 Xcode 自动管理证书(不需要手动导 P12) │
│ │
└────────────────────────────────────────────────────┘
二、重签名
当我们测试设备达到上限(100台时),重签是解决思路之一,这里梳理下重签名的大体过程:
此时原签名已失效] end subgraph C [CI/CD 自动重签名阶段] C1[安装证书与私钥到
临时钥匙串] C2[提取权限文件
Entitlements] C3[调用 codesign 命令
对修改后的包重新签名] C4[打包生成 IPA] end subgraph D [分发与部署] D1[上传 TestFlight] D2[部署到内网分发平台
蒲公英/fir.im 等] end A1 & A2 & A3 --> C1 B1 --> B2 --> B3 --> C3 C1 --> C2 --> C3 --> C4 --> D1 C4 --> D2
参考文章: ios逆向-重签名