第六板块:Android 安全与权限体系 | 第二十篇:应用签名、权限机制与 PackageManagerService 的安全校验

第六板块:Android 安全与权限体系 | 第二十篇:应用签名、权限机制与 PackageManagerService 的安全校验

所属板块:第六板块 --- Android 安全与权限体系

前置知识:第十九篇中的 SELinux 强制访问控制、Linux DAC 权限、应用沙箱机制、Binder IPC

本篇定位 :这是 Android 应用生态的信任锚点 。如果说 SELinux 是系统内部的防弹衣,那么 应用签名 就是应用之间的身份证与防伪码 。本篇将彻底拆解 APK 签名方案(v1/v2/v3/v4)的密码学原理证书链(Certificate Chain)的信任模型PackageManagerService (PMS) 的安装校验流水线权限的动态授予与 UID 映射SharedUserId 的信任边界 。我们将深入 JarSignerAPK Signature Scheme v2PMS 的 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 (结尾)

校验流程

  1. PMS 读取 ZIP End of Central Directory。
  2. 定位 APK Signing Block。
  3. 提取签名证书和公钥。
  4. 对整个 APK 文件(除签名块外)计算摘要。
  5. 使用公钥解密签名,比对摘要。

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)

覆盖安装必须满足以下条件:

  1. 包名相同(Package Name)。
  2. 签名证书相同(Certificate Digest)。
  3. 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 存储架构

相关推荐
KKKlucifer1 小时前
智能研判、本地运算、一键运维:新一代安全管控产品的三大核心能力
运维·安全
朝星1 小时前
Android开发[11]:启动优化
android·kotlin
AI玫瑰助手1 小时前
Python函数:函数的文档字符串(docstring)编写
android·java·python
德迅云安全杨德俊1 小时前
游戏盾的原理解析与游戏盾的优势特点
安全·游戏·ddos
JohnnyDeng941 小时前
【Android】Android渲染机制:Choreographer与VSYNC深度解析
android·性能优化·kotlin·jetpack
xhtdj1 小时前
BadHost 漏洞使AI 代理评估器和LLM 网关面临风险
安全
zyplayer-doc1 小时前
zyplayer-doc 文档管理系统深度评测:私有化部署下的安全与智能边界
安全·开源软件
恋猫de小郭1 小时前
Flutter 又为 AI 时代添砖加瓦:全新 ComponentLibrary 提议
android·前端·flutter