上周在调一个系统应用权限问题同事又跑来问同一个问题Framework这边怎么查看某个system应用到底用的是哪个签名这个问题听起来不难但真做起来还是有点绕因为它横跨了包管理服务、签名方案、证书解析好几个环节。你光会 adb 拉 APK 看指纹还不够想在 Framework 层把“到底用哪个签名”这件事搞清楚还得知道 PackageManager 内部是怎么取签名的、dumpsys 里那串 hex 到底是什么以及为什么有时候 keytool 和设备上显示的结果对不上。这篇文章我就按自己实际踩坑的顺序来写从最直接的命令行查法到源码里的定位方式最后给出一套能直接编译运行的签名查看工具帮你在 Android Framework 定制和系统应用排查时快速定位签名问题。内容比较适合做 ROM 开发、Framework 定制、系统应用预装或者签名权限验证的开发者。1. 为什么要在Framework里查system应用签名1.1 系统应用签名到底决定了什么在 Android 里签名不是可有可无的 meta 信息它直接决定了一个应用能拿到哪些系统级能力。普通应用之间签名不同会导致互相不能覆盖安装但系统应用的签名影响更广主要体现在三块。第一块是权限授予。AOSP 框架里有一类权限叫做signature级别权限常见的android.permission.SIGNATURE或者系统本身定义的权限只有和系统使用同一个签名的应用才能拿到。例如很多SystemApi接口、系统服务 binder 调用都要求调用方必须有 platform 签名否则即使你在 manifest 里写了权限也拿不到。Framework 层在给应用授予权限时底层就是比对 APK 的签名和系统保存的平台签名是否一致。第二块是包替换和升级。系统应用如果想在 OTA 后保留数据或者通过包管理服务替换掉旧的预装应用旧包和新包的签名必须一致或者至少满足签名轮转规则。很多 OTA 刷机失败或开机后某个系统应用出现 force close实际上就是签名不一致导致的。第三块是共享 UID。Android 里可以利用sharedUserId把多个应用放到同一个进程但前提是它们的签名一致。Framework 在构建共享 UID 组时会对所有同 UID 应用做签名校验只要有一个签名不对整个组的安装就会失败。所以当你发现一个系统应用行为异常时第一反应往往是怀疑它的签名到底是不是 platform。比如你用系统权限接口时报了SecurityException或者把某个 APK 放进/system/priv-app后它没有拿到privileged权限这时候确认签名就变成了排查问题的第一步。1.2 什么场景需要精确到Framework层去查有人可能会说查签名直接用 keytool 或 apksigner 解包不就完了吗干嘛非要扯上 Framework确实如果只是为了确认一个 APK 文件本身的签名用本地工具最方便。但实际开发中有几种情况你只能通过 Framework 侧的手段来查设备已经在跑系统应用已经安装到内存里但系统分区里的 APK 文件可能被 OTA 更新、动态分区、或者厂商定制改过。你拉出来的 APK 文件不一定就是运行时正在运行的那个版本。你关心的是 PackageManagerService 眼里看到的签名而不是 APK 文件里的表面签名。因为 PackageManager 解析签名时可能涉及 v1/v2/v3 选择、签名轮转、多签名者处理只有从PackageInfo或者 PMS 内部拿到的才是它真正用来做权限判断的那份数据。你自己正在写 Framework 层代码比如修改 PackageManagerService、SystemServer 初始化逻辑或者做一个系统权限判断模块需要在代码里动态比较签名。这时候你不可能停下来去用 keytool必须知道怎么在框架代码里获取并输出签名。所以这个问题的本质不是“如何验证 APK”而是“如何让 Framework 告诉你它记录的签名是什么”。2. 先把签名这件事的基本概念理清楚2.1 签名、证书、指纹三者不是一回事很多人在查签名时栽跟头就是因为三个概念混在一起。APK 的签名本质上是个数字签名签名时用私钥对 APK 内容做摘要和加密然后把签名和公钥证书一起放进包里。我们在代码里通过Signature对象拿到的通常是证书的 DER 编码字节。Framework 或者 keytool 显示出来的一长串 hex就是这个证书的原始字节转十六进制有时候长达上千个字符。而证书指纹是证书字节做 SHA-1/SHA-256 之后得到的短摘要一般是 40 或 64 位 hex。keytool 打印的 SHA256 指纹、apksigner 打印的certificate SHA-256 digest都指的是这个短摘要。Framework 里 dumpsys 输出的signatures: [...]往往不是指纹而是证书 DER 的 hex 串。如果直接拿 keytool 的指纹去和 dumpsys 的输出比对肯定对不上这是最常见的困惑点。所以你在往后对比时要么让两边都输出指纹要么让两边都输出 DER hex不能混着来。2.2 系统里的platform、shared、media、testkey签名Android 系统预先准备了好几套调试用的证书和私钥位于源码的build/target/product/security/目录下常见的几个platform平台签名system_server、Settings、SystemUI 等核心应用通常都用它。拥有 platform 签名的应用可以拿到大多数signature级别的系统权限。shared共享签名用于多个应用需要共享 UID 或共用进程的场景比如一些厂商内置应用。media媒体签名给部分多媒体相关应用或服务使用。testkey默认测试签名一些不限定签名的应用会用。在源码编译系统应用时你会在 Android.bp 或者 Android.mk 里看到类似certificate: platform的配置这表示编译时用platform.x509.pem和platform.pk8对 APK 签名。Framework 里的权限判断尤其是signature级权限的授予还会额外校验应用签名是否匹配系统预置的关键签名。需要注意的是不同厂商在量产时会换成自己的签名证书不一定沿用 AOSP 默认的platform。所以你在源码里看到platform这个标签只能说明构建配置最终的签名内容还是要以实际 APK 或运行时信息为准。2.3 v1/v2/v3签名方案对查证方式的影响Android 从早期到如今APK 签名方案经历了多次迭代。v1 是传统的 JAR 签名文件放在META-INF/下v2 在 Android 7.0 引入签名块嵌在 APK 文件里v3 在 Android 9 引入支持签名轮转。这个迭代对查签名的影响非常直接如果你拿 keytool 去解析一个只做了 v2/v3 签名的 APK它可能看不到有效的 v1 签名证书但 Framework 安装校验时用 v2/v3 的签名块去验证。反过来一个老应用可能只有 v1 签名设备端显示的是 v1 证书你用 apksigner 却看到 Signer 信息因为 apksigner 默认也能兼容 v1。所以为了拿到 Framework 同一份数据最稳妥的方法是使用PackageManager的GET_SIGNING_CERTIFICATES获取SigningInfo或者用 apksigner 查看 APK 的所有有效签名信息而不是依赖 keytool 看META-INF里的老证书。这一块不用背太多只要记住在 Framework 层看签名优先看SigningInfo不要臆想它一定等于某一种方案里的证书。3. 最快上手用现成命令确认系统应用签名3.1 adb shell dumpsys package 直接看签名摘要最快的办法不是写代码而是直接让包管理服务自己把签名吐出来。命令很简单adb shell dumpsys package com.android.phone | grep -i signature如果包名不确定可以先用pm list packages筛一下。执行后你会看到类似这样的输出signatures: [308205c8308203b0a00302010202080d...]这一大串字符就是证书 DER 编码的十六进制文本。如果你用Signature.toCharsString()打印出来看到的也是同样的字符串因为dumpsys在底层调用的就是PackageSetting里保存的Signature对象。在部分高版本 Android 上pm dump可能只输出部分签名信息或者因为签名轮转出现多行。你可以加-A多显示几行adb shell dumpsys package com.android.phone | grep -A 3 -i signatures拿到这串 hex 后想要验证它是不是 platform 签名可以用源码里的平台证书做转换openssl x509 -in build/target/product/security/platform.x509.pem -outform DER | xxd -p | tr -d \n如果两串 hex 完全一致那就说明当前设备上这个应用使用的确确实实是 platform 签名。这个方法不需要 root只是很多时候你拿不到源码里的.x509.pem或者系统镜像被厂商改过证书这时就要换用本地 APK 解析的方式。3.2 本地用apksigner/keytool解析APK证书如果你的设备允许把系统应用 APK 拉出来或者你有刷机包里的system.img可以把 APK 提取到本地再解析。先从设备拉包adb pull /system/app/Phone/Phone.apk Phone.apk有的设备上应用目录可能是/system/priv-app/Phone/Phone.apk具体看dumpsys package com.android.phone里codePath字段。拉出来以后优先用 build-tools 里的 apksignerapksigner verify --print-certs -v Phone.apk输出内容大致是Signer #1 certificate DN: CUS, STCalifornia, L..., OAndroid, OUAndroid, CNAndroid Signer #1 certificate SHA-256 digest: f5c1... Signer #1 certificate SHA-1 digest: 55a9...这里的certificate SHA-256 digest就是证书指纹。如果你已经从dumpsys拿到了 DER hex可以本地把 platform 证书转成 SHA-256 指纹来对比openssl x509 -in platform.x509.pem -outform der | sha256sum要注意的是sha256sum输出的 hex 可能有换行或空格直接肉眼对比容易看漏。你可以用awk {print $1}只取第一段。另外apksigner给出的指纹是全小写sha256sum输出也是小写可以直接比对。keytool 也可以用比如keytool -printcert -jarfile Phone.apk但 keytool 偏向读 v1 签名的证书信息如果 APK 只做了 v2/v3 签名它可能看不到你要找的签名者。所以系统应用查签名我一般优先 apksigner。3.3 如何判断是否为platform签名判断方法其实就一句话把设备/ Framework 拿到的签名信息和源码里 platform 证书的对应信息做比对一致就是 platform不一致就不是。但这里有几个细节值得注意。第一对比证书指纹时要注意算法。apksigner 输出 SHA-256 digestdumpsys输出 DER hex两者不能直接比。要统一成指纹就把 DER hex 解码后做 SHA-256要统一成 DER hex就把平台证书从 PEM 转成 DER 输出十六进制。第二Android 9 以后系统可能有签名轮转。一个应用当前的签名证书未必是历史签名证书Framework 在做签名校验时通常会考虑整个历史证书链。如果你发现当前签名的 SHA-256 指纹和 platform 不一致但应用又能正常拿到系统权限就很可能是签名轮转把 old signer 声明为 platform 证书导致的。这种情况需要用SigningInfo.getSigningCertificateHistory()查看历史证书不能只看当前 signer。第三dumpsys package显示的是已经安装到设备上的包信息如果系统分区里同时存在两个相同包名但签名不同的 APKpm显示的是实际被加载的那一个别被源码目录里静态看的 APK 迷惑。4. Framework层源码与运行时查签名的正确姿势4.1 从构建脚本看签名LOCAL_CERTIFICATE如果你能拿到系统源码最直观的“查签名”方式其实是看应用的构建配置。在 AOSP 里一个系统应用的 Android.bp 长这样android_app { name: Settings, srcs: [src/**/*.java], // ... certificate: platform, }certificate字段指定编译时使用的签名证书名称。常见值包括platform、shared、media、testkey系统会去build/target/product/security下找对应的.x509.pem和.pk8文件进行签名。这种方式的好处是快缺点是不够真实。厂商可能在自己的定制分支里改了某个应用的certificate配置也可能是产品 makefile 里通过PRODUCT_DEFAULT_CERTIFICATE修改了默认证书还可能整个 APK 已经不是从源码编译出来的而是 vendor 单独放进去的预编译产物。这时候源码配置就只能作为辅助线索最终还是要看设备运行时的签名内容。另外一个常见的坑有些应用在 Android.bp 里没有显式写certificate系统会使用PRODUCT_DEFAULT_CERTIFICATE这个值通常在产品配置里设置。如果你在应用目录里搜不到certificate不要急着下结论先去搜一下产品 config 里的默认值。4.2 PackageManager获取签名的API演进与正确写法Framework 层获取应用签名的标准入口是PackageManager。在 Android 9API 28之前大家习惯用PackageInfo info pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES); Signature[] sigs info.signatures;这段代码能跑但现在已经不推荐了。因为GET_SIGNATURES拿到的签名数组在一些场景下只反映旧的签名方式处理不了 v2/v3 签名块和签名轮转。官方从 API 28 开始引入了GET_SIGNING_CERTIFICATES和SigningInfo。正确的写法是PackageManager pm context.getPackageManager(); PackageInfo info pm.getPackageInfo(pkgName, PackageManager.GET_SIGNING_CERTIFICATES); if (info.signingInfo null) { Log.w(SignCheck, signingInfo is null); return; } Signature[] signers info.signingInfo.getApkContentsSigners(); if (signers ! null) { for (Signature s : signers) { Log.i(SignCheck, current signer: s.toCharsString()); } } Signature[] history info.signingInfo.getSigningCertificateHistory(); if (history ! null) { for (Signature s : history) { Log.i(SignCheck, history signer: s.toCharsString()); } }这里要理解getApkContentsSigners()和getSigningCertificateHistory()的区别。前者返回 APK 内容里当前使用的签名证书数组在正常情况下是一个元素如果应用使用了签名轮转历史证书数组会包含更早的签名证书。Framework 在做权限校验时会同时考虑这些签名证书。另外如果目标应用和其他应用共享 UIDgetPackageInfo返回的可能是共享 UID 组的签名信息。你需要确定自己是关心单个 APK 的签名还是关心共享 UID 组整体必须一致的签名别混着看。4.3 在system_server里打印签名有时候你想看的不是某个应用自己能拿到的签名而是 PackageManagerService 在解析 APK 时看到的原始签名。这种情况下直接在 PMS 源码里加打印是最直接的。在 AOSP 的PackageManagerService中APK 扫描期间会调用PackageParser解析包并收集证书。你可以在解析完成后的代码路径里临时加一段日志把包名和签名输出到 logcat。不同 Android 版本的代码位置不一样但思路一致在scanPackageLI或类似的扫描方法里pkg对象已经包含解析出的签名信息直接打印即可。打印代码大致长这样if (pkg ! null pkg.getSigningInfo() ! null) { Signature[] sigs pkg.getSigningInfo().getApkContentsSigners(); if (sigs ! null) { Log.w(SignDump, scan pkg.packageName codePath pkg.codePath signers Arrays.toString(sigs)); } }这里用pkg.getSigningInfo()是一个比较安全的访问方式具体字段名要看当前源码的PackageParser.Package定义。在 Android 12 之后的版本签名信息通常挂在Package对象的mSigningInfo或通过getSigningInfo()方法访问不同分支略有差异编译的时候遇到没有的方法就顺着源码搜一下。如果你不想编译整个系统镜像只想快速验证某个应用签名的底层信息也可以利用system_server里的IPackageManager接口通过ServiceManager拿到 binder 之后调getPackageInfo。但这种方式本质上还是走 PackageManager 的封装和 4.2 节写应用代码拿到的东西没有区别只是身份换成了系统服务。5. 实战写一个Framework侧签名查看小工具5.1 设计思路与前置条件我平时在 AOSP 源码里调试签名问题不喜欢频繁改 PMS 代码因为编译一次太慢。更高效的办法是写一个带 platform 签名的系统应用装到/system/priv-app下运行后把目标包名列表的签名全部打出来。每个 Android 版本差异不大改起来也方便。这个工具的核心思路很简单用PackageManager.getPackageInfo加GET_SIGNING_CERTIFICATES拿SigningInfo然后分别打印当前签名者和历史签名者。需要满足两个前置条件目标 APK 是可以被应用查询到的。如果这个工具位于/system/priv-app且使用 platform 签名它在查询绝大多数系统应用时不会因为包可见性问题被拦截。目标包必须是已安装应用拉不到包名的可以用pm list packages先确认。关于放在/system/priv-app而不是普通应用目录是因为部分signature级权限和系统接口只有系统应用能调用也是为了更大程度避免包可见性限制。签名本身查询未必需要特权权限但你的目的既然是做 Framework/系统破解向排查干脆一步到位。5.2 代码实现可直接编译运行在 AOSP 的packages/apps/SignatureDump目录下创建一个非常简单的 Activity。Android.bp内容android_app { name: SignatureDump, srcs: [src/**/*.java], platform_apis: true, certificate: platform, privileged: true, }src/com/android/sigdump/SignatureDump.java内容package com.android.sigdump; import android.app.Activity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Bundle; import android.util.Log; import java.util.Arrays; public class SignatureDump extends Activity { private static final String TAG SignDump; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dump(com.android.phone); dump(com.android.settings); dump(com.android.systemui); dump(getPackageName()); finish(); } private void dump(String pkg) { try { PackageManager pm getPackageManager(); PackageInfo info pm.getPackageInfo( pkg, PackageManager.GET_SIGNING_CERTIFICATES); if (info.signingInfo null) { Log.w(TAG, pkg signingInfo is null); return; } Signature[] signers info.signingInfo.getApkContentsSigners(); if (signers ! null signers.length 0) { Log.w(TAG, pkg current signers count signers.length); for (int i 0; i signers.length; i) { Log.w(TAG, pkg current signer[ i ] signers[i].toCharsString()); } } Signature[] history info.signingInfo.getSigningCertificateHistory(); if (history ! null history.length 0) { Log.w(TAG, pkg history signers count history.length); for (int i 0; i history.length; i) { Log.w(TAG, pkg history signer[ i ] history[i].toCharsString()); } } } catch (Exception e) { Log.e(TAG, dump failed for pkg, e); } } }这段代码在 AOSP 环境直接编译成系统应用后推送到设备的/system/priv-app/SignatureDump/SignatureDump.apk重启或者用adb install安装系统应用最好随镜像刷入或者用adb root后手动 push 并设置权限。然后拉起 Activityadb shell am start -n com.android.sigdump/.SignatureDump随后抓日志adb logcat -s SignDump你会看到类似这样的输出SignDump: com.android.phone current signers count1 SignDump: com.android.phone current signer[0]308205c8308203b0... SignDump: com.android.settings current signers count1 ...这个 DER hex 和dumpsys package里的signatures:字段理论上应该一致因为它们都来自同一个Signature对象。5.3 运行结果与输出解读如果你把输出和源码里的 platform 证书 DER 对比就能快速确认应用是否使用 platform 签名。一个小技巧是直接在代码里把 platform 证书的 SHA-256 也打印出来省得每次拿 openssl 去算。可以在 AOSP 源码里放一张platform.x509.pem然后在工具启动时读出来计算指纹CertificateFactory cf CertificateFactory.getInstance(X.509); Certificate cert cf.generateCertificate(new FileInputStream(platform.x509.pem)); byte[] encoded cert.getEncoded(); MessageDigest md MessageDigest.getInstance(SHA-256); byte[] digest md.digest(encoded); Log.w(TAG, platform sha256 bytesToHex(digest));bytesToHex自己写个简单的工具方法就行。这样设备上跑一次就能同时看到目标应用的指纹和平台证书指纹做权限问题排查时特别省事。这里还要提醒一下应用身份是 platform 签名并不代表它就能为所欲为。系统权限的授予还要看应用有没有声明对应的权限、是否位于/system/priv-app、是否满足privileged权限白名单等。所以签名只是排查条件之一别看到 platform 就下结论。6. 常见坑与排查实录速查6.1 dumpsys拿到的签名和keytool对不上这是出现频率最高的问题。你adb pull出 APK用 keytool 打印了一串 SHA256 指纹回头对比 dumpsys 里的一长串 hex发现格式都不一样直接开始怀疑设备是不是有问题。原因我们前面已经提到keytool 打印的是证书指纹摘要而 dumpsys 直接打印的是证书 DER 编码 hex。两者不是同一种格式自然对不上。解决办法有两个方向要么都用 apksigner 的 SHA-256 digest 对比要么都用 DER hex 对比。我推荐前者因为 apksigner 能看到 v2/v3 的签名者更接近 Framework 使用的数据。执行命令时如果apksigner verify --print-certs输出里有两个 Signer说明 APK 存在多个签名者可能是密钥轮转。需要把两个 Signer 的指纹都记录下来再逐个和 dumpsys 里的签名信息做对应。6.2 GET_SIGNATURES偶尔报错或拿到的不是最新签名旧代码里经常用PackageManager.GET_SIGNATURES拿info.signatures。这个字段没有被移除但在签名轮转或多签名者场景下容易提供不完整信息。比如你只看到第一个签名证书但实际上 Framework 校验时可能用的是另一份签名证书。更麻烦的是部分系统接口在尚未适配新 API 时调用GET_SIGNATURES可能触发 deprecation 警告个别厂商 ROM 做了兼容性处理返回的签名数组是截断的。排查这类问题最高效的做法是升级到GET_SIGNING_CERTIFICATES并同时打印getApkContentsSigners()和getSigningCertificateHistory()把所有候选证书都拿出来。GET_SIGNING_CERTIFICATES是从 API 28 开始支持的如果你的目标系统低于 Android 9就还是只能走GET_SIGNATURES。遇到这种情况建议把info.signatures的每个元素都打出来同时用apksigner从 APK 文件侧做交叉验证。6.3 有多个签名者时怎么正确比较我在 5.2 节的工具里特意打印了 signers count 和历史签名者就是为了处理这种情况。签名轮转在系统应用升级中并不罕见厂商在系统应用上签了新证书但为了让旧数据能保留会在签名块里声明上一代证书为历史签名者。这时 Framework 做权限校验时通常会看整个历史证书链也就是说只要历史证书里包含 platform 证书应用就可能继续持有系统权限哪怕当前证书指纹不是 platform。所以当你发现当前 signer 指纹和 platform 对不上先别急着判定应用没有系统权限把历史签名者的指纹也打印出来看看里面有没有 platform 证书。对应地如果两个应用的当前签名不同但历史签名链有交叉它们在签名校验时也可能被认为匹配。别只看一个证书要看整个链。6.4 用户态查不到、权限不够时的替代方案有些系统应用查询时会出现SecurityException常见原因是普通应用没有目标包的可见性。从 Android 11 开始包可见性规则更严格普通应用用getPackageInfo查其他应用可能直接被拦掉。如果你写独立应用查签名记得在 manifest 里加queries声明或者直接把应用做成系统应用用 platform 签名安装到/system/priv-app这样基本不会遇到包可见性问题。如果使用的是 userdebug/eng 版本还可以先adb root再尝试adb shell pm dump。release 版本没有 root 权限时/system分区可能不可读这时候想从 APK 文件层做解析就不太现实只能依赖我们已经装好的系统签名工具或者 PMS 日志输出来取证。最后再分享一个我自己的小技巧在给 OEM 客户定位系统应用签名问题时我习惯先在 dumpsys 上快速确认signatures:的 hex再在本地解包 APK 用 apksigner 输出同一条证书的 SHA-256 指纹最后把两个结果转成openssl命令的输出放到同一个文件里 diff。只要做到格式统一签名匹配性结论基本十秒钟就能下。容易被忽略的反而是那些前处理工作确认 codePath 对得上、确认 APK 版本和系统实际加载版本一致、确认历史签名者也被纳入考量。把这几步做好Framework 下查 system 应用签名真不是什么大工程。