先看结论与判断条件

  • 签名校验是资源完整性的前置条件,但仅证明 APK 文件未被第三方重打包,不能证明资源值符合业务预期语义。
  • 单独计算 resources.arsc 的摘要可与签名无关,提供对构建流水线内部篡改或意外修改的检测能力。
  • 运行时动态资源替换应通过显式允许列表与摘要校验,并严格区分于常规业务配置下发流程。
  • 工程判断:跨构建维护允许清单时优先使用资源名称而非数值 ID;ID 是否稳定须由当前 aapt2 版本和两份实际产物对比确认。
  • 供应链证明(provenance)将资源摘要绑定到构建上下文,使验证链从文件签名延伸到构件创建过程。
  • 校验失败时应按安全策略立即阻断,并记录详细日志以便溯源,避免盲目进入不可控的降级状态。

资源篡改的威胁模型与职责边界

攻击者通过二次打包替换 resources.arsc 内的字符串、图标或数值,可改变界面展示、功能分支或业务逻辑关联的资源值。若业务代码根据资源值做权限判断或价格计算,篡改可导致直接经济损失。这类修改发生在构建发布之后,因此签名校验最先感知,但若攻击者利用自定义证书重新签名,签名校验即失效,需要资源值层面的完整性与语义验证。

构建环境中的恶意脚本或错误合并也可能在 APK 签名前将错误资源植入。此类情形下签名有效,但资源已非预期。开发者必须依赖构建流水线中的资源摘要记录与供应链证明,将资源产物与构建配置绑定。只有这样才能区分签名有效的合法 APK 和签名有效但内容被污染的 APK。

运行时动态资源加载(如通过热修复、网络下发)绕过了 APK 签名与静态资源表的保护。这类场景应将动态资源视作独立安全边界,引入单独签名和允许列表。对于此层面的篡改,静态 resources.arsc 检查无法覆盖,必须在运行时建立验证点,并确保隔离动态资源对核心资源 ID 映射的污染。

明确职责边界有助于避免过度防御或防御缺失。静态资源表的完整性由构建系统和签名机制共同保障,而运行时加载的外部资源则完全依赖应用自身的校验逻辑。混淆两者会导致安全策略错位,例如用签名校验去验证动态下发的配置,或用运行时检查去防御已签名的静态包体篡改。

资源篡改分类与归责表
篡改类型注入阶段签名有效?内容检查责任运行时责任
resources.arsc 直接替换打包后分发前否(重签名后可变有效)静态摘要比对
构建中资源注入构建阶段需摘要比对 + 证明
运行时动态替换运行时签名不覆盖不适用运行时上下文校验
额外资源文件添加(非 arsc)二次打包arsc 摘要无法检测可与签名结合
  • 是否已识别二次打包检测策略
  • 构建系统是否产生并存储资源摘要
  • 动态资源加载清单是否已独立审核

resources.arsc 结构与篡改可操作面

resources.arsc 是二进制资源表,由一系列 chunk 组成,包含表头、字符串池、包信息、类型定义和条目列表,每个条目指向具体的资源值或引用。攻击者可修改字符串池内容而不改变长度,替换指向的偏移,或在类型条目中插入新的配置限定符,从而在特定设备上加载未授权值。

由于资源的查找依赖 ID(0xPPTTEEEE)和配置限定符,对 entry 顺序的重排或对 type 结构的插入可能会改变业务代码中 R.class 的映射。如果应用使用了反射或 getIdentifier,攻击者还可以通过伪造资源名称映射来重定向资源请求。因此,完整性检查不能只依赖 ID 比对,应当结合资源名称和配置上下文校验。

解析 resources.arsc 时必须严格处理大端字节序、字符串长度编码和 chunk 长度字段,否则可能被特意构造的畸形 chunk 触发缓冲区越界或解析适配器错误。在完整性检查中,首选使用 aapt2 dump 或其底层库来生成标准化摘要,而避免手写解析器在安全工具链中产生新攻击面。

AOSP ResourceTypes 定义了这些二进制结构的布局,包括 chunk 类型与长度字段。从工程实践来看,自行解析底层结构需要跟随格式变化维护。实际项目可优先调用官方构建工具导出稳定的中间结果,再将其作为值级清单的输入,减少自制解析器误读。

  • 确认使用的资源解析库来源与完整性
  • 检查是否仅依赖资源 ID
  • 是否处理畸形 chunk 导致偏移越界

签名校验的覆盖与限制

APK 签名方案 v2/v3 通过在 APK Signing Block 中保存签名数据覆盖 ZIP 文件内除签名块本身之外的几乎所有内容,包括 resources.arsc、manifest 和 DEX 文件。验证工具(apksigner verify)可确认签名是否与证书链匹配以及签名方案是否允许降级。但签名校验成功只表明文件未被修改过,并不能保证其内容原先就符合预期。

如果开发者在签名前生成并验证资源摘要,并将该摘要作为 APK 内容的一部分由签名保护,那么就可以把对内容正确性的信任从构建环境扩展到分发渠道。然而,签名不保护 APK 之外通过应用商店元数据或动态下载的资源,所以对 runtime 资源不可依靠签名。

Android 仍兼容 v1 签名,但资源完整性策略不能只看某一种方案是否存在。验收脚本应输出每个签名方案的验证结果,再按应用的 minSdk 和渠道要求决定最低接受组合;不要让运行时代码从自身 APK 推导预期策略。

apksigner 的结论限于当前文件的签名与证书信息,并不证明私钥保管流程安全。本文不涉及 keystore 管理流程的具体细节;资源表检查还需把提取值与独立维护的允许清单比对,不能把签名通过当成业务配置正确。

关键资源允许列表设计原则

允许列表应明确列出所有在安全决策中引用的静态资源,例如控制支付环境、API 端点、许可开关或调试标志的资源 ID 或名称。不应将整个资源表纳入允许列表,以减少误报并保留业务配置演进所需的弹性。过度严格的列表会增加维护负担,导致正常业务迭代受阻。

对于每个条目,需确定其唯一标识方法:使用资源名称(如 com.example.app:string/api_endpoint)比资源 ID 更稳定,可避免因 R 类重新生成导致 ID 变更。摘要算法应选择抗碰撞的 SHA256,并与资源值和配置限定符一同计算,防止只替换特定配置限定的资源版本。

允许列表本身以及对应的预期摘要应版本化,并存入 APK 的 assets 或 raw 目录,这样签名保护同时覆盖列表。构建系统将该列表与 resources.arsc 的解析输出一并生成,并记入供应链证明中。当列表变动时,需同步更新证明并触发额外的安全评审,确保变更可追溯。

工程建议优先使用资源名称作为标识键,因为 ID 的稳定性依赖于构建输入的严格排序。若必须使用 ID,则需在构建系统中锁定资源文件的处理顺序。这种设计权衡直接影响允许列表的长期可维护性,需在项目初期明确技术选型。

允许列表条目设计决策表
决策项选项 A选项 B工程建议
资源标识粒度资源 ID资源名称优先使用名称,若必须用 ID 则锁定构建
摘要包含范围仅值字符串值 + 配置限定符 + 类型包含配置与类型以发现限定符替换
摘要算法SHA256BLAKE3SHA256 已足够且生态支持广
列表存放方式硬编码assets 动态读取存入 assets 内由签名保护
  • 清单是否按资源名称标识
  • 是否包含对应的配置限定符
  • 摘要算法是否记录并保持一致
  • 清单是否版本化并与构建证明绑定

摘要清单的生成、嵌入与校验

在构建阶段,通过在 aapt2 link 后立即调用自定义工具解析 resources.arsc,遍历指定资源名称列表,提取实际值,计算各值的摘要并组装为 JSON。该工具应基于 AOSP ResourceTypes 定义或 aapt2 的内部格式,避免引入其他解析器导致的偏差。这一步骤是建立可信基线的关键。

生成的清单 JSON 必须随 APK 打包进 assets 目录,使签名同时覆盖。清单中每个条目不仅包含资源名和摘要,还应包含配置限定符列表和构建生成时的工具版本信息,以便后续审计。构建系统最好将清单的摘要存储于 provenance,记录到构建来源证明以便追溯。

应用冷启动时可从 APK 读取清单,并通过同一套资源解析逻辑获取关键资源的实际值,计算摘要后比对。任何不匹配都要记录资源名称、预期摘要与实际摘要,再由既定策略决定停止功能或使用安全默认值。预期清单不能与待检查 APK 一起由同一不可信输入生成。

代码实现需注意异常处理,避免因资源缺失或格式错误导致应用崩溃。校验过程应在主线程之外异步执行,或在应用初始化早期同步完成,具体取决于业务对启动速度的容忍度。无论何种方式,都必须确保在校验完成前不加载受保护的资源值。

资源表完整性摘要校验脚本
#!/usr/bin/env python3
import sys
import hashlib
import zipfile

def compute_sha256(filepath):
    sha256 = hashlib.sha256()
    with open(filepath, 'rb') as f:
        while True:
            data = f.read(65536)
            if not data:
                break
            sha256.update(data)
    return sha256.hexdigest()

def main():
    if len(sys.argv) != 3:
        print("Usage: verify_arsc.py <apk_file> <expected_hash>")
        sys.exit(2)
    apk_path = sys.argv[1]
    expected_hash = sys.argv[2]
    try:
        with zipfile.ZipFile(apk_path, 'r') as z:
            arsc_info = z.getinfo('resources.arsc')
            with z.open(arsc_info) as arsc:
                sha = hashlib.sha256()
                while True:
                    data = arsc.read(8192)
                    if not data:
                        break
                    sha.update(data)
                actual_hash = sha.hexdigest()
                if actual_hash != expected_hash:
                    print("MISMATCH: resources.arsc integrity check failed")
                    sys.exit(1)
                else:
                    print("resources.arsc integrity verified")
    except KeyError:
        print("ERROR: resources.arsc not found in APK")
        sys.exit(1)
    except Exception as e:
        print(f"ERROR: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

运行时区分篡改、业务配置与缓存

业务配置往往通过远程配置中心、粘性广播或 SharedPreferences 下发,这些值可能影响资源选择或界面行为,但不能直接修改 resources.arsc。运行时校验必须将动态配置与静态资源分开处理:对于静态资源,只接受由签名保护清单认可的值;对于动态配置,应使用独立的完整性保护(如 HMAC 或数字签名)并保持审计日志。

缓存可能保存了上一次资源检查的中间状态或经过允许的替换值,因此需要设计缓存有效期和刷新策略。一旦检测到 resources.arsc 摘要与预期不符,应立即清除与其关联的缓存,防止残留的合法资源被恶意替换后的代码引用。缓存一致性是防止旧数据污染的重要环节。

失败条件包括:清单文件缺失、清单无法解析、资源提取异常、摘要不匹配、签名校验未通过等。每个条件需对应清晰的处理动作,例如阻断启动、限制网络权限、擦除用户数据或仅通知监测系统。必须将处理动作记录在安全事件日志或远程遥测中,以便事件回溯。

区分静态与动态资源的另一个好处是减少误报。业务频繁调整配置时,若未正确隔离,会触发大量静态资源校验告警。通过明确的边界定义,运维团队可以快速定位问题根源,是构建产物被篡改,还是配置下发出现了异常,从而采取正确的响应措施。

  • 是否在代码中区分静态资源与动态配置
  • 缓存失效策略是否跟随资源完整性状态
  • 失败动作是否按安全等级明确定义

供应链证明中的资源摘要绑定

SLSA Provenance v1.1 要求构建证明绑定产物 identity、构建器、构建类型和输入材料。在资源完整性场景中,可以将 resources.arsc 的摘要作为 subject 之一,或作为单个产物的材料列表中的一个条目,确保有人篡改构建输出时证明无法通过验证。这增强了构建过程的可信度。

in-toto Attestation 的 Statement 模型允许将产物摘要与声明负载绑定。资源允许清单的摘要可以与构建信息组合成一个 in-toto 声明,由构建系统签名,然后由部署端或审计系统在启动时交叉验证,进一步闭环比对环节到构建环境的信任链。这种机制能有效防止构建产物被调包。

供应链证明本身并不取代运行时资源校验。证明只能证实构建过程的宣称,若运行时使用了其他由更新服务下发的资源包,则需要另建证明机制。工程建议是始终将静态核心资源的摘要嵌入 APK 并受签名保护,同时将同一摘要纳入 provenance,形成双层校验。

接入供应链证明需要在 CI/CD 中增加摘要采集、声明签名与验证步骤。改造工作量视现有流水线复杂度而定,此处不做具体估算。实施时先为单一 release 变体绑定 resources.arsc 摘要与构建参数,验证回执可复现后再扩展到其他变体。

资源完整性检查方法对比
方法检测范围依赖项局限
APK 签名验证整个 APK 文件apksigner / 证书链无法检测签名前污染,不覆盖外部资源
独立 arsc 文件摘要仅 resources.arsc构建时摘要记录不检测其他文件,需额外工具
关键资源 ID/值比对特定资源值允许清单与解析器需维护清单,漏项不覆盖
供应链证明绑定从源码到产物可信构建环境与记录只能证实构建过程,非运行时验证

端到端验证 checklist 与失败处置

安全团队应建立端到端验证流程,从 CI 签名、资源摘要生成、清单嵌入、APK 分发到应用启动检查逐环节校验。使用自动化脚本对每个发布候选 APK 执行签名验证、摘要比对和允许清单一致性测试。自动化能减少人为疏忽,确保每次发布都经过严格审查。

对于生产环境中检测到的失败,应有分级响应:签名失败、arsc 摘要不匹配、关键资源值异常属于严重事件,需立即阻断上线或强制更新;业务配置摘要变化属于警告,但若不在预期清单内也应及时复核;检查基础设施自身异常应触发安全告警并回退至最近一次可信状态。

应明确记录所有失败的证据,包括 APK 哈希、签名证书指纹、出错的资源名和值、时间戳和环境信息,以便于安全回溯和判定事故边界。所有日志需要防篡改保护(如远程上报并签名)并设定保留期。完整的证据链是事后分析和定责的基础。

定期回顾校验策略的有效性至关重要。随着业务发展和攻击手法演变,允许列表和校验逻辑可能需要调整。建立定期的复盘机制,分析误报和漏报案例,不断优化检测规则,确保持续适应新的安全挑战。

  • 各环节是否已自动化执行完整性检查
  • 失败分级策略是否涵盖签名、摘要、允许清单异常
  • 失败证据日志是否启用并防篡改

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
Android 二进制资源表由带长度和类型的 chunk、package、type 与 entry 结构组成。AOSP ResourceTypes固定历史源码用于解释格式结构,实际构建仍应以当前 aapt2 输出为准。
resources.arsc 内字符串池与资源条目通过偏移关联,解析时需严格验证 chunk 长度和标志防止越界。AOSP ResourceTypes手写解析器易引入偏移计算缺陷,生产环境应优先采用 SDK 工具。
apksigner 可签名、验证方案覆盖与证书,并要求签名后不再修改 APK。Android apksigner工具验证成功不证明 keystore 管理、渠道流程或候选包归档正确。
APK Signature Scheme v2 通过 APK Signing Block 覆盖文件主体,并包含防止降级到较弱方案的机制。APK Signature Scheme v2v2 不覆盖独立于 APK 分发的外部资产。
APK 签名方案(v1/v2/v3)允许应用选择性启用 v1 签名兼容性,但验证者应拒绝仅 v1 签名的 APK 以防范降级攻击。APK Signature Scheme v2降级保护要求验证方代码实现正确校验所有方案标志。
Manifest 定义组件、权限、intent filter、SDK 约束和应用元数据。Android app manifest静态清单不能证明运行时访问控制没有被业务代码放宽。
构建证明应绑定产物主体、构建者、构建类型、外部参数与依赖材料。SLSA Provenance v1.1provenance 只能证明记录的构建过程,不能单独证明运行时安全性。
供应链证明应把产物摘要与有类型的声明负载绑定,避免报告与候选包错配。in-toto Attestation Statement v1声明格式不保证声明内容真实,仍需可信签名者和门禁核验。

工程常见问题

APK 签名已通过,为何还要校验 resources.arsc 摘要?

签名只能证明 APK 未被第三方篡改,但无法防止构建链引入的错误或已在签名环节前植入的异常值。单独校验资源摘要可将完整性前移至构建产物,与签名互补。

业务动态下发配置导致资源变化,如何避免误报?

应对动态配置建立独立的渠道与签名审计,并将变化登记至允许列表的扩展区域,或在运行时上下文标记,确保核心静态资源不允许未登记修改。

资源 ID 在跨版本构建中会漂移,如何维护允许列表?

可以将允许列表锚定在资源名称或文件名上,并在构建时生成摘要与命名映射,由供应链证明锁定具体的资源 ID 产出。只有在 aapt2 输入源完全锁定且顺序稳定时才可使用 ID。

多渠道打包时,渠道号可能写入资源,如何确保摘要一致性?

应将渠道信息作为构建参数记录在 provenance 中,资源摘要覆盖除渠道字段外的确定性部分,或采用按渠道分离的摘要清单。

生成的摘要清单自身需要签名吗?

必须纳入 APK 签名保护范围,即清单应置于 APK 内,使得签名同时绑定清单与资源。独立分发清单则需要独立的可信交付与校验机制。

校验失败后应用应该立即退出还是降级运行?

对于安全级别高的应用,应立即阻断并上报;对于需要高可用的业务,可设立受控降级策略,例如限制功能但记录证据,且降级策略须经安全评审。运行时上下文必须记录事件并防止重放。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: APK、DEX 与签名完整性如何一起验收