第六板块: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 如何从数学上保证应用来源的真实性和完整性。全程无签名教程、无权限申请指南,仅保留 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 §** | APK 签名块 | 整个 APK 文件 | 支持密钥轮换。允许开发者在签名块中证明新旧密钥的关联性,解决密钥丢失问题。 |
| v4 (Streaming Sign) | API 30 ®** | .idsig 文件 |
仅 APK 内容 | 支持增量安装。签名存储在单独的文件中,方便流式传输。 |
2.2 v2 签名方案的深度解析
v2 是目前最主流的方案,它改变了签名的结构。
学术定义:
- APK 签名分块(APK Signing Block):位于 ZIP 中央目录之前,文件内容之后。包含签名数据(证书、公钥、算法)。
- 完整性覆盖 :v2 对 APK 的 所有字节(除了签名块本身)计算摘要。这包括了 ZIP 的 Local File Header、Central Directory 等元数据。
#mermaid-svg-HnHlynoFeVU8zFwq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HnHlynoFeVU8zFwq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HnHlynoFeVU8zFwq .error-icon{fill:#552222;}#mermaid-svg-HnHlynoFeVU8zFwq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HnHlynoFeVU8zFwq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HnHlynoFeVU8zFwq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HnHlynoFeVU8zFwq .marker.cross{stroke:#333333;}#mermaid-svg-HnHlynoFeVU8zFwq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HnHlynoFeVU8zFwq p{margin:0;}#mermaid-svg-HnHlynoFeVU8zFwq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HnHlynoFeVU8zFwq .cluster-label text{fill:#333;}#mermaid-svg-HnHlynoFeVU8zFwq .cluster-label span{color:#333;}#mermaid-svg-HnHlynoFeVU8zFwq .cluster-label span p{background-color:transparent;}#mermaid-svg-HnHlynoFeVU8zFwq .label text,#mermaid-svg-HnHlynoFeVU8zFwq span{fill:#333;color:#333;}#mermaid-svg-HnHlynoFeVU8zFwq .node rect,#mermaid-svg-HnHlynoFeVU8zFwq .node circle,#mermaid-svg-HnHlynoFeVU8zFwq .node ellipse,#mermaid-svg-HnHlynoFeVU8zFwq .node polygon,#mermaid-svg-HnHlynoFeVU8zFwq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HnHlynoFeVU8zFwq .rough-node .label text,#mermaid-svg-HnHlynoFeVU8zFwq .node .label text,#mermaid-svg-HnHlynoFeVU8zFwq .image-shape .label,#mermaid-svg-HnHlynoFeVU8zFwq .icon-shape .label{text-anchor:middle;}#mermaid-svg-HnHlynoFeVU8zFwq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HnHlynoFeVU8zFwq .rough-node .label,#mermaid-svg-HnHlynoFeVU8zFwq .node .label,#mermaid-svg-HnHlynoFeVU8zFwq .image-shape .label,#mermaid-svg-HnHlynoFeVU8zFwq .icon-shape .label{text-align:center;}#mermaid-svg-HnHlynoFeVU8zFwq .node.clickable{cursor:pointer;}#mermaid-svg-HnHlynoFeVU8zFwq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HnHlynoFeVU8zFwq .arrowheadPath{fill:#333333;}#mermaid-svg-HnHlynoFeVU8zFwq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HnHlynoFeVU8zFwq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HnHlynoFeVU8zFwq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HnHlynoFeVU8zFwq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HnHlynoFeVU8zFwq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HnHlynoFeVU8zFwq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HnHlynoFeVU8zFwq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HnHlynoFeVU8zFwq .cluster text{fill:#333;}#mermaid-svg-HnHlynoFeVU8zFwq .cluster span{color:#333;}#mermaid-svg-HnHlynoFeVU8zFwq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HnHlynoFeVU8zFwq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HnHlynoFeVU8zFwq rect.text{fill:none;stroke-width:0;}#mermaid-svg-HnHlynoFeVU8zFwq .icon-shape,#mermaid-svg-HnHlynoFeVU8zFwq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HnHlynoFeVU8zFwq .icon-shape p,#mermaid-svg-HnHlynoFeVU8zFwq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HnHlynoFeVU8zFwq .icon-shape .label rect,#mermaid-svg-HnHlynoFeVU8zFwq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HnHlynoFeVU8zFwq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HnHlynoFeVU8zFwq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HnHlynoFeVU8zFwq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} APK 文件结构
Contents of ZIP entries (文件内容)
APK Signing Block (v2/v3 签名)
ZIP Central Directory (目录)
ZIP End of Central Directory (结尾)
校验流程:
- 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 安装流程全景图
SELinux Policy PackageVerifier Installer Service PackageManagerService 用户/应用商店 SELinux Policy PackageVerifier Installer Service PackageManagerService 用户/应用商店 #mermaid-svg-pw6YuSJzWpbDnviv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pw6YuSJzWpbDnviv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pw6YuSJzWpbDnviv .error-icon{fill:#552222;}#mermaid-svg-pw6YuSJzWpbDnviv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pw6YuSJzWpbDnviv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pw6YuSJzWpbDnviv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pw6YuSJzWpbDnviv .marker.cross{stroke:#333333;}#mermaid-svg-pw6YuSJzWpbDnviv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pw6YuSJzWpbDnviv p{margin:0;}#mermaid-svg-pw6YuSJzWpbDnviv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pw6YuSJzWpbDnviv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-pw6YuSJzWpbDnviv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pw6YuSJzWpbDnviv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-pw6YuSJzWpbDnviv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-pw6YuSJzWpbDnviv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-pw6YuSJzWpbDnviv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-pw6YuSJzWpbDnviv .sequenceNumber{fill:white;}#mermaid-svg-pw6YuSJzWpbDnviv #sequencenumber{fill:#333;}#mermaid-svg-pw6YuSJzWpbDnviv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-pw6YuSJzWpbDnviv .messageText{fill:#333;stroke:none;}#mermaid-svg-pw6YuSJzWpbDnviv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pw6YuSJzWpbDnviv .labelText,#mermaid-svg-pw6YuSJzWpbDnviv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-pw6YuSJzWpbDnviv .loopText,#mermaid-svg-pw6YuSJzWpbDnviv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-pw6YuSJzWpbDnviv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pw6YuSJzWpbDnviv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pw6YuSJzWpbDnviv .noteText,#mermaid-svg-pw6YuSJzWpbDnviv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-pw6YuSJzWpbDnviv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pw6YuSJzWpbDnviv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pw6YuSJzWpbDnviv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pw6YuSJzWpbDnviv .actorPopupMenu{position:absolute;}#mermaid-svg-pw6YuSJzWpbDnviv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-pw6YuSJzWpbDnviv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pw6YuSJzWpbDnviv .actor-man circle,#mermaid-svg-pw6YuSJzWpbDnviv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-pw6YuSJzWpbDnviv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt验证失败验证通过 installPackage()验证 APK 签名1. 证书有效性2. 完整性校验 (v2/v3)验证通过/失败安装失败 (INSTALL_FAILED_INVALID_APK)解析 Manifest检查权限声明分配 UID 和 GID分配安全上下文 (seapp_contexts)上下文确认复制 APK 到 /data/app解压原生库 (.so)优化 Dex (dex2oat)更新 packages.xml安装成功
4.2 关键校验源码解析
1. 签名验证 (PackageParser.java)
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)
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 的数据库,记录了所有已安装应用的信息。
xml
<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 存储架构