第六板块:Android 安全与权限体系 | 第二十篇:应用签名、权限机制与 PackageManagerService 的安全校验
所属板块:第六板块 — Android 安全与权限体系
前置知识:第十九篇中的 SELinux 强制访问控制、Linux DAC 权限、应用沙箱机制、Binder IPC
本篇定位:这是 Android 应用生态的信任锚点。如果说 SELinux 是系统内部的防弹衣,那么 应用签名 就是应用之间的身份证与防伪码。本篇将彻底拆解 APK 签名方案(v1/v2/v3/v4)的密码学原理、证书链(Certificate Chain)的信任模型、PackageManagerService (PMS) 的安装校验流水线、权限的动态授予与 UID 映射、SharedUserId 的信任边界。我们将深入 JarSigner、APK Signature Scheme v2、PMS 的 Installer 服务,揭示 Android 如何从数学上保证应用来源的真实性和完整性。
1. 核心结论先行(Thesis Statement)
Android 的应用安全是一个基于公钥基础设施(PKI)的信任链。
- 应用签名的本质:数字指纹。开发者使用私钥对 APK 进行签名,系统使用公钥验证 APK 是否被篡改,以及是否来自同一开发者。
- PMS 的本质:系统信任的守门人。它在应用安装(Install)和升级(Update)时,执行严格的四重校验:证书校验、完整性校验、权限兼容性校验、SELinux 上下文分配。
- 权限的本质:能力的映射。系统将
AndroidManifest.xml中声明的权限,映射为 Linux 的 GID(Group ID)。当应用请求权限时,PMS 将其加入对应的 GID,内核通过 DAC 控制访问。 - SharedUserId 的本质:打破沙箱的协议。它允许两个应用运行在同一个 UID 下,共享数据和进程空间,但这要求两者必须有相同的签名证书。
2. APK 签名方案演进史
2.1 签名方案的对比
Android 支持多种签名方案,新方案是为了解决旧方案的安全和性能问题。
| 方案 | 引入版本 | 签名位置 | 完整性保护 | 学术定义 |
|---|---|---|---|---|
| v1 (Jar Signature) | API 1 | META-INF/MANIFEST.MF | 仅文件内容 | 基于 Java Jar 签名,只对文件内容做摘要,不保护 ZIP 元数据。易被篡改(ZipBomb)。 |
| v2 (Full-file Sign) | API 24 (N)** | APK 签名块 (ECDSA/PKI) | 整个 APK 文件 | 在 APK 中央插入一个签名块。任何对 APK 的修改都会破坏签名。推荐方案。 |
| v3 (Key Rotation) | API 28 (P)** | APK 签名块 | 整个 APK 文件 | 支持密钥轮换。允许开发者在签名块中证明新旧密钥的关联性,解决密钥丢失问题。 |
| v4 (Streaming Sign) | API 30 (R)** | .idsig 文件 | 仅 APK 内容 | 支持增量安装。签名存储在单独的文件中,方便流式传输。 |
2.2 v2 签名方案的深度解析
v2 是目前最主流的方案,它改变了签名的结构。
学术定义:
- APK 签名分块(APK Signing Block):位于 ZIP 中央目录之前,文件内容之后。包含签名数据(证书、公钥、算法)。
- 完整性覆盖:v2 对 APK 的 所有字节(除了签名块本身)计算摘要。这包括了 ZIP 的 Local File Header、Central Directory 等元数据。
graph LR
subgraph APK ["APK 文件结构"]
direction LR
A["Contents of ZIP entries (文件内容)"]
B["APK Signing Block (v2/v3 签名)"]
C["ZIP Central Directory (目录)"]
D["ZIP End of Central Directory (结尾)"]
end
A --> B
B --> C
C --> D
校验流程:
- PMS 读取 ZIP End of Central Directory。
- 定位 APK Signing Block。
- 提取签名证书和公钥。
- 对整个 APK 文件(除签名块外)计算摘要。
- 使用公钥解密签名,比对摘要。
3. 证书与密钥体系
3.1 密钥对与证书
| 组件 | 学术定义 | 作用 |
|---|---|---|
| 私钥 (Private Key) | RSA/ECDSA 密钥 | 开发者持有,绝对保密。用于对 APK 进行签名。 |
| 公钥 (Public Key) | 私钥对应的公开部分 | 包含在证书中。系统用它验证签名。 |
| 证书 (Certificate) | X.509 标准 | 包含公钥、颁发者、有效期、所有者信息。 |
3.2 证书指纹(Fingerprint)
证书指纹是证书的哈希值,用于唯一标识一个证书。
学术定义:
- SHA-256: 目前的标准。例如
SHA256: AB:12:CD:34... - MD5/SHA1: 已废弃,存在碰撞风险。
PMS 的信任逻辑:
- 安装时:PMS 计算证书指纹,存入
/data/system/packages.xml。 - 升级时:PMS 比对旧证书指纹和新证书指纹。
- 相同:允许升级(同一开发者)。
- 不同:拒绝升级(可能被篡改或冒充)。
4. PackageManagerService (PMS) 的安装校验流水线
4.1 安装流程全景图
sequenceDiagram
participant User as 用户/应用商店
participant PMS as PackageManagerService
participant Installer as Installer Service
participant Verifier as PackageVerifier
participant SELinux as SELinux Policy
User->>PMS: installPackage()
PMS->>Verifier: 验证 APK 签名
Verifier->>Verifier: 1. 证书有效性
Verifier->>Verifier: 2. 完整性校验 (v2/v3)
Verifier-->>PMS: 验证通过/失败
alt 验证失败
PMS-->>User: 安装失败 (INSTALL_FAILED_INVALID_APK)
else 验证通过
PMS->>PMS: 解析 Manifest
PMS->>PMS: 检查权限声明
PMS->>PMS: 分配 UID 和 GID
PMS->>SELinux: 分配安全上下文 (seapp_contexts)
SELinux-->>PMS: 上下文确认
PMS->>Installer: 复制 APK 到 /data/app
Installer->>Installer: 解压原生库 (.so)
Installer->>Installer: 优化 Dex (dex2oat)
PMS->>PMS: 更新 packages.xml
PMS-->>User: 安装成功
end
4.2 关键校验源码解析
1. 签名验证 (PackageParser.java)
// frameworks/base/core/java/android/content/pm/PackageParser.java
private static void collectCertificates(Package pkg, File apkFile, int flags) {
// 1. 尝试 v2/v3 签名
ApkSignatureSchemeV2Verifier.verify(apkFile);
// 2. 如果失败,尝试 v1 签名
JarVerifier.verify(apkFile);
}
2. 权限与 UID 映射 (PackageManagerService.java)
// 分配 UID
int uid = mSettings.getUidForSharedUser(pkg.mSharedUserId);
if (uid < 0) {
uid = mSettings.newUserId(pkg);
}
pkg.applicationInfo.uid = uid;
// 映射权限到 GID
for (String perm : pkg.requestedPermissions) {
if (perm.equals("android.permission.INTERNET")) {
// 将应用加入 inet GID
pkg.applicationInfo.gids = appendGid(pkg.applicationInfo.gids, GID_INET);
}
}
5. 权限机制深度解析
5.1 权限的分类与等级
| 权限等级 | 声明方式 | 授予时机 | 学术定义 |
|---|---|---|---|
| Normal | normal | 安装时自动授予 | 低风险,不影响隐私(如 INTERNET)。 |
| Dangerous | dangerous | 运行时请求 | 高风险,涉及隐私(如 READ_CONTACTS)。 |
| Signature | signature | 安装时自动授予 | 仅当请求者和声明者签名相同时才授予。 |
| System | system | 安装时自动授予 | 仅系统应用(UID < 10000)可获得。 |
5.2 Runtime Permission 的动态授予
Android 6.0 (M) 引入了运行时权限。
学术定义:
- 权限组(Permission Group):将相关权限分组(如
CONTACTS组包含读/写联系人)。用户授权一个组,组内所有权限生效。 - 动态决策:应用调用
requestPermissions(),系统弹窗,用户选择。PMS 记录用户的选择到/data/system/users/0/runtime-permissions.xml。
5.3 SharedUserId 的信任边界
android:sharedUserId="android.uid.system"
学术定义:
- 打破沙箱:两个应用共享同一个 UID,意味着它们可以互相访问私有数据,甚至可以运行在同一个进程中。
- 签名约束:只有签名与系统证书相同的应用才能使用
android.uid.system。这是 OEM 厂商预装应用的特权。 - 风险:一旦应用获得 SharedUserId,它就脱离了普通应用的沙箱限制。
6. 应用更新的安全逻辑
6.1 覆盖安装(Cover Installation)
覆盖安装必须满足以下条件:
- 包名相同(Package Name)。
- 签名证书相同(Certificate Digest)。
- SharedUserId 兼容(如果声明了 SharedUserId,必须一致)。
6.2 降级攻击防御
学术定义:
- Version Code 校验:PMS 检查新 APK 的
versionCode必须大于或等于旧 APK。 - 防降级:如果攻击者试图用旧版本覆盖新版本(修复漏洞),PMS 会拒绝,除非使用
-d参数(调试模式)。
7. 关键数据结构与存储
7.1 packages.xml
这是 PMS 的数据库,记录了所有已安装应用的信息。
<package name="com.example.app" codePath="/data/app/com.example.app-1" nativeLibraryPath="/data/app/com.example.app-1/lib" primaryCpuAbi="arm64-v8a" publicFlags="805306896" privateFlags="0" ft="186a0" it="186a0" ut="186a0" version="101" userId="10001">
<sigs count="1">
<cert index="1" key="308203..." />
</sigs>
<perms>
<item name="android.permission.INTERNET" granted="true" flags="0" />
<item name="android.permission.READ_CONTACTS" granted="false" flags="0" />
</perms>
</package>
7.2 packages.list
便于人类阅读的列表,用于快速查找。
com.example.app 10001 /data/app/com.example.app-1 platform 0 1
8. 常见安全漏洞与防御
| 漏洞 | 学术定义 | Android 防御 |
|---|---|---|
| Janus 漏洞 | 在 APK 前拼接 DEX 文件,利用 Zip 解析差异执行恶意代码。 | v2 签名保护整个 APK,无法拼接。 |
| Master Key 漏洞 | 构造同名文件,利用 Zip 解析漏洞欺骗签名验证。 | v2 签名不依赖文件名,基于文件内容。 |
| 权限提升 | 声明 protectionLevel="signature" 的权限,诱导系统应用声明,然后调用。 | PMS 检查权限定义者和请求者的签名是否一致。 |
9. 本篇总结(Knowledge Closure)
| 关键点 | 纯学术定义 |
|---|---|
| 应用签名的本质 | 基于 PKI 的数字指纹,保证完整性和来源真实性。 |
| PMS 的角色 | 系统信任守门人,执行证书、完整性、权限、上下文的四重校验。 |
| 权限映射 | 将 Manifest 权限映射为 Linux GID,通过 DAC 控制访问。 |
| SharedUserId | 打破沙箱的协议,基于相同签名的 UID 共享。 |
| v2 签名 | 全文件完整性保护,防御 Zip 解析漏洞。 |
10. 第六板块结语
至此,第六板块:Android 安全与权限体系 已全部完结。
我们从 SELinux 的强制访问控制 出发,深入 内核 LSM 钩子,探索 安全上下文与 TE 策略,最终抵达 应用签名的 PKI 信任链 与 PMS 的安装校验。
我们揭示了 Android 安全的最高准则:“永不信任,始终验证”。
下一篇预告:第七板块:Android 存储体系与文件系统 | 第二十一篇:Vold 与 FUSE 存储架构