
做ROM定制或者做企业设备应用开发的朋友大概率会撞上这么一件事你写了一个功能很基础的系统管理类App比如切换网络策略、备份系统设置、实时监听按键本地调试一点问题都没有一装到定制系统上就各种SecurityException甚至logcat里明晃晃写着Permission Denial: requires android.permission.WRITE_SECURE_SETTINGS。这个时候别急着改逻辑先想想是不是签名不对。Android系统签名Platform Signature就是解决这类问题的钥匙。普通应用签名只能证明这个App是你写的系统签名则能让系统把你的App当成自己人直接放行一大票高敏感权限和系统级API。这篇文章我会把系统签名的原理、制作密钥库的完整命令、APK签名方式、预置到系统分区的配置以及我踩过的各种坑从头到尾讲一遍。适合正在做AOSP定制、开发板适配、企业安全设备方案的Android工程师也适合刚接触ROM开发、想搞明白系统权限体系的新手。1. 系统签名到底是怎么回事1.1 普通签名和系统签名的区别Android里任何一个APK都必须签名才能安装这是Android安全模型的地基。应用签名本质上是用你的私钥对应用内容做摘要签名系统在安装时用证书里的公钥验证完整性确保这个Apk没有被篡改过。这是所有Android应用都逃不掉的一步。而系统签名特殊在它使用的不是开发者自己随便生成的密钥而是编译系统镜像时内置在AOSP源码里那套固定的密钥。系统在启动后会持有这套公钥信息。一个APK如果用对应的私钥签名系统就会认为这个应用的身份和系统本身是同源的于是把它当作系统组件对待可以分配更高等级的用户ID、授权系统级权限。1.2 为什么应用需要系统签名很多关键权限在AndroidManifest里标注为protectionLevelsignature或者signature|privileged比如WRITE_SECURE_SETTINGS、MOUNT_UNMOUNT_FILESYSTEMS、PACKAGE_USAGE_STATS这类。普通应用即使你在manifest里写了权限系统grant权限的时候也会直接拒绝因为你的签名不在它信任的列表里。这时候应用唯一的出路就是用系统签名给自己签一道让系统认可你的身份。另外一类场景是android:sharedUserIdandroid.uid.system也就是把应用跑在system用户组下共享系统进程的UID。这个设计在Android早期很常用很多需要调用系统隐藏接口、直接操纵Framework服务的App都靠它实现。但要使用这个UID你的App签名必须是系统签名否则PackageManager直接报INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。1.3 系统签名的适用范围先泼盆冷水系统签名不是拿到就能为所欲为的万能钥匙。对于正式零售设备因为开启了Verified Boot、dm-verity等完整性校验机制你无法刷入一个跟你系统签名不一致的镜像更不可能只用系统签名让App绕过系统校验。这个方案主要适用于开发者自己的刷机包、工程机、开发板、企业定制的无锁设备。对于应用商店上架Google Play或国内应用市场有自己的签名机制和审核体系你不需要也不能使用系统签名。对于普通App开发团队如果只是做一个装机量可观的常规应用用系统签名反而埋雷后续维护和升级都会很麻烦因为签名一旦丢失或者被泄露安全风险非常大。所以系统签名的正确使用场景非常聚焦你要在你自己有完全控制权的系统固件上让一个应用获得系统级信任。2. 准备签名材料和工具2.1 AOSP里的四把钥匙如果你下载过AOSP源码在build/target/product/security/目录下能看到几个固定的密钥文件。内核签字、系统签名、测试签名都在这里。最常用的四组是platform.pk8 / platform.x509.pem平台签名也就是系统签名大部分系统核心App都用这把media.pk8 / media.x509.pem媒体服务器签名涉及MediaProvider等shared.pk8 / shared.x509.pem共享签名某些公共进程使用testkey.pk8 / testkey.x509.pem默认测试签名未明确指定证书的模块默认用它制作系统签名核心就是拿到platform.pk8私钥和platform.x509.pem证书。2.2 本地开发环境的工具清单没有源码也没关系只需要以下工具OpenSSL用来转换密钥格式。Linux/Mac自带Windows建议装Git自带的openssl或者从slproweb下载JDK的keytool一般在$JAVA_HOME/bin下装好JDK就有Android SDK Build-Tools里的apksigner路径在$ANDROID_SDK/build-tools/version/apksigner最好加到PATH里这里有个容易忽略的点apksigner和keytool不在同一个工具链里。keytool负责管理keystoreapksigner负责真正的APK签名。很多人卡在“生成keystore”这一步其实本质是没搞懂pk8/x509.pem、P12、JKS这三种格式之间的转换关系下面我把完整流程走一遍。3. 把pk8/x509.pem制作成Android Studio可用的keystoreAndroid Studio的signingConfigs最方便的状态是提供一个JKSJava KeyStore或PKCS12格式的密钥库文件。AOSP给的pk8和x509.pem不能直接导入Android Studio三者之间需要做两三次格式转换。先看整体链路platform.pk8 platform.x509.pem ↓ openssl platform.pemPEM私钥 ↓ openssl platform.p12PKCS12密钥库 ↓ keytool platform.jksJKS密钥库3.1 第一步把platform.pk8转成PEM私钥pk8是DER编码的PKCS#8私钥首先把它变成PEM编码的私钥文件openssl pkcs8 -inform DER -nocrypt -in platform.pk8 -out platform.pem执行完可以用head -1 platform.pem检查如果输出是-----BEGIN PRIVATE KEY-----说明转换成功。这里注意-nocrypt参数不能去掉因为AOSP的pk8本身没有密码加密去掉反而会进入交互式加密流程后面就用不上了。3.2 第二步生成PKCS12密钥库把证书和私钥打包成一个带别名的PKCS12文件openssl pkcs12 -export -in platform.x509.pem -inkey platform.pem -name platform -password pass:android -out platform.p12参数解释一下-name platform是别名alias后续在keytool和Gradle里都必须一致-password pass:android是为PKCS12设置存储密码很多AOSP项目的通用约定是android你也可以改成自己的但后面每步都用同一个这一步我遇到过最多的坑是OpenSSL 3.x版本兼容性问题。3.x默认加密算法变了生成的P12文件拿到旧版Java的keytool里会报unsupported algorithm或者keystore password was incorrect。解决方案是加一个-legacy参数或者换用OpenSSL 1.x再执行一次openssl pkcs12 -export -legacy -in platform.x509.pem -inkey platform.pem -name platform -password pass:android -out platform.p123.3 第三步导入JKS用keytool把P12转成JKS格式keytool -importkeystore -deststorepass android -destkeypass android -destkeystore platform.jks -srckeystore platform.p12 -srcstoretype PKCS12 -srcstorepass android -srcalias platform -destalias platform这条命令的意思是从platform.p12读别名platform的密钥对写入platform.jks存储密码和密钥密码都是android。如果你用的是JDK 9以上版本默认密钥库类型是PKCS12。如果你确实想要JKS可以在命令里加-storetype JKS。不过其实Android Studio对PKCS12和JKS都支持扩展名用jks还是p12不是关键关键是你配置的storeFile路径和密码要对上。到这里你已经有一个可以直接放到Android Studio项目里使用的platform.jks。把它放到工程目录下比如app/platform.jks然后继续看怎么签名。4. 用系统签名给APK签名4.1 用apksigner以最快的方式签名如果你只是要把一个已有的APK签成系统签名的APK完全不需要先生成JKS直接用apksigner配合pk8/x509.pem走一遍就行apksigner sign --key platform.pk8 --cert platform.x509.pem --out app-signed.apk app-unsigned.apk这条命令直接把原始APK签名成带系统证书的应用。默认情况下apksigner会同时启用v1和v2签名方案对于Android 7.0以上的设备足够用了。如果你的App targetSdk达到Android 9或更高建议显式确认v2签名生效apksigner sign --key platform.pk8 --cert platform.x509.pem --v1-signing-enabled true --v2-signing-enabled true --out app-signed.apk app-unsigned.apk验证签名是否真的成功、证书内容是否符合预期用apksigner verify --verbose --print-certs app-signed.apk正常你会看到证书DN类似CUS, STCalifornia, LMountain View, OGoogle Inc., OUAndroid, CNAndroid这就是AOSP默认平台证书标识。4.2 在Android Studio里配置系统签名如果你的项目是直接通过Gradle构建那就在app模块的build.gradle里添加signingConfigs。最标准的写法是android { signingConfigs { platform { storeFile file(platform.jks) storePassword android keyAlias platform keyPassword android } } buildTypes { release { signingConfig signingConfigs.platform minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } debug { signingConfig signingConfigs.platform } } }这里有个细节debug构建也配上系统签名能让你在开发阶段直接以系统身份跑起来省去反复打release包的痛苦。配置完以后执行./gradlew assembleRelease或者直接点Build Generate Signed APK选择release签名配置生成的APK就带系统签名了。4.3 签名之后还要做什么如果APK打算预置到系统镜像里除了签名还要考虑包名、权限声明、编译方式这几件事。签名只解决“身份”问题不解决“安装位置”问题。系统App要真正活得像系统App还得看它住在哪个分区、拥有哪些权限声明。这正是下一节的内容。5. 系统权限与sharedUserId的配合5.1 sharedUserId怎么用在Android早期的系统App开发里几乎绕不开这个写法。manifest根节点里加一行manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.systemapp android:sharedUserIdandroid.uid.system uses-permission android:nameandroid.permission.WRITE_SECURE_SETTINGS / uses-permission android:nameandroid.permission.MOUNT_UNMOUNT_FILESYSTEMS / ... /manifest加上android.uid.system之后应用会以system用户身份运行可以直接调用很多普通应用碰不到的系统API比如Settings.Secure的写操作、DevicePolicyManager里的部分隐藏接口、系统级的电源管理接口等等。但注意这只是声明。要让系统接受这个sharedUserId你的签名必须是系统签名两者缺一不可。如果签名不对安装时就会报INSTALL_FAILED_SHARED_USER_INCOMPATIBLE。5.2 高版本系统的限制但凡用sharedUserId的人应该都踩过Android 10和Android 11的坑。官方从Android 10API 29开始把sharedUserId标记为deprecatedAndroid 11API 30之后限制更加严格targetSdkVersion 30及以上、同时声明了sharedUserId的应用安装时会被系统拒绝。我实测下来的现象就是targetSdkVersion 29及以下sharedUserId还能用但不推荐targetSdkVersion 30及以上安装直接失败报INSTALL_FAILED_SHARED_USER_INCOMPATIBLE如果你的业务必须用sharedUserId一个取巧的方法是把targetSdkVersion降到29但我不建议新项目这么做因为Google Play上架和国内很多应用市场都对targetSdkVersion有强制要求。更好的做法是用priv-app方案。5.3 更推荐的方案signature权限 priv-app在Android 9以后的定制系统里我更推荐用“安装到priv-app目录 声明signature权限 配置权限白名单”这套组合拳。它不需要sharedUserId也能拿到绝大多数的特权权限。具体做法是在AndroidManifest里声明权限uses-permission android:nameandroid.permission.WRITE_SECURE_SETTINGS /然后把这个APK预置到/system/priv-app/下。priv-app本身就是系统特权应用目录系统会对这里的应用赋予privileged级别的权限。但是Android 9之后增加了一项保护机制priv-app要使用privileged权限必须在系统的权限白名单文件里显式声明包名和权限清单否则即使装到priv-app也会被降权。白名单文件通常在源码的frameworks/base/data/etc/privapp-permissions-platform.xml里添加或者放在产品的overlay目录中privapp-permissions packagecom.example.systemapp permission nameandroid.permission.WRITE_SECURE_SETTINGS/ permission nameandroid.permission.MOUNT_UNMOUNT_FILESYSTEMS/ /privapp-permissions这种方案的优势是干净、可控不需要共享进程UID也避开了高版本系统对sharedUserId的封锁。代价是你必须保证应用确实被安装到了priv-app目录这就要看下一节的系统镜像预置流程了。6. 把App预置成系统应用6.1 Android.bp / Android.mk里的签名配置如果你手上就是一份完整AOSP源码那么把系统签名的APK打进系统镜像有两种方式源码模块编译和预置APK。源码模块编译推荐用Android.bpSoong新构建系统android_app { name: MySystemApp, srcs: [src/**/*.java], resource_dirs: [res], manifest: AndroidManifest.xml, certificate: platform, privileged: true, installable: true, }其中certificate: platform这行非常关键它告诉构建系统用platform密钥来签名这个模块。privileged: true则负责把APK安装到/system/priv-app目录。如果你需要放到product分区现在很多厂商推荐这样可以加product_specific: true加proprietary: true。如果你手里只是一个编译好的APK不用重新编译整个项目那就用预置方式。Android.mk写法如下LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : MySystemApp LOCAL_MODULE_CLASS : APPS LOCAL_MODULE_TAGS : optional LOCAL_BUILT_MODULE_STEM : package.apk LOCAL_SRC_FILES : MySystemApp.apk LOCAL_MODULE_PATH : $(TARGET_OUT_PRODUCT)/priv-app/MySystemApp LOCAL_CERTIFICATE : platform include $(BUILD_PREBUILT)LOCAL_MODULE_PATH决定最终装到哪个目录LOCAL_CERTIFICATE : platform决定用系统签名。6.2 别忘了权限白名单很多人在这一步翻车APK成功装到了priv-app权限也在manifest里写了但运行时还是拿不到特权权限。十有八九就是权限白名单没配。配白名单有两种方式修改frameworks/base/data/etc/privapp-permissions-platform.xml在产品目录里放一个额外的privapp-permissions-xxx.xml文件然后通过PRODUCT_COPY_FILES或者overlay机制把它拷到/system/etc/permissions/下我习惯用第二种因为不会侵入frameworks/base方便不同产品差异化配置。文件内容格式一样permissions privapp-permissions packagecom.example.systemapp permission nameandroid.permission.WRITE_SECURE_SETTINGS/ /privapp-permissions /permissions文件复制到/system/etc/permissions/privapp-permissions-product.xml然后确保它以permissions作为根节点。最后重新编译系统镜像、刷机验证。如果一切正常你的App在系统里的运行身份就已经是system级很多以前被SecurityException拦住的操作都能直接跑通。7. 常见问题与坑我把实际开发中收集到的问题整理成了一张速查表基本覆盖了系统签名使用过程中90%的异常情况。错误现象根本原因解决方案INSTALL_FAILED_SHARED_USER_INCOMPATIBLE应用声明了sharedUserIdandroid.uid.system但APK签名不是platform确认签名用了platform证书确认targetSdkVersion在29及以下SecurityException: Permission Denial应用要求系统级权限但应用没有正确安装到priv-app或白名单缺失检查APK安装路径是否为/system/priv-app检查privapp-permissions白名单是否声明当前包名和权限keytool导出时报keystore password was incorrectOpenSSL 3.x生成的P12与旧版keytool不兼容执行openssl pkcs12 -export时加-legacy参数apksigner: Unable to open ... as keystore用apksigner签名时没有指定--key/--cert而是误把pk8/x509.pem当keystore传了直接使用--key platform.pk8 --cert platform.x509.pemJava.io.IOException: Invalid keystore formatkeytool默认输出类型和实际文件类型不一致在keytool命令里显式加-storetype JKS或统一使用PKCS12签名成功但安装到设备上系统图标带个小绿人/签名校验失败应用在编译过程中被Android Studio自动签名覆盖检查Gradle的signingConfigs是否真的生效尤其是debug版本默认会被debug.keystore签名应用能adb shell pm list packages看到但Settings里找不到可能是proguard混淆了Application类或者APK没有正确声明Activity先检查APK本身是否能独立安装运行排除签名问题后再排查代码分享一个排查顺序的实操心得遇到权限相关的奇怪问题我会先做三步自查apksigner verify --print-certs app.apk确认证书CN确实是Androidadb shell ps | grep 包名确认进程的UID是不是systemuid1000adb shell dumpsys package 包名 | grep versionName确认包的安装路径和flags里有没有systempriv权限按照这个顺序查90%的问题都能定位到具体环节。另外密钥文件保管真的是件大事。platform签名意味着拿到这个私钥的人就能制作出在你的系统固件上拥有system权限的应用。在企业做项目的时候我都会把platform.pk8和platform.x509.pem放在独立的加密仓库里只给指定开发机拉取权限不给所有工程师随意下载。真要出了密钥泄露那就不只是换一个签名的事整个系统的信任体系都要重建代价非常大。我个人在实际操作中的体会是系统签名本质上不是什么高深魔法它只是Android安全模型里“同源信任”的一种体现。一旦理解了这套机制你会发现Android系统的权限系统其实非常顺理成章你信任什么你才授予什么。对于做定制系统的人来说这是最重要也最需要敬畏的一把钥匙合理使用能大幅提升开发效率滥用则会给整个系统埋下巨大的安全隐患。希望这篇文章能帮你把系统签名这条路彻底走通。