资讯动态

Android APP签名指纹获取:keytool与apksigner

发布时间:2026/10/28 5:09:01 来源:尧图企业网站定制
做安卓这行迟早会碰上要报签名的时刻。接第三方推送、地图、支付、分享这类 SDK 的时候后台注册页面总会伸手向你要一串东西MD5、SHA1有的还要 SHA256填错了就是初始化失败、登录拉不起来、地图白屏。很多人第一次遇到这个需求是在交付前一夜翻遍项目目录才想起来 keystore 不知道被谁放在哪儿也有人的 APK 是从别人手里接过来的签名文件根本拿不到只能从安装包里反查。这篇文章就把Android Studio 获取 APP 应用签名这件事从头到尾捋一遍把 MD5/SHA1/SHA256 的来龙去脉讲清楚把keytool 和 apksigner两条主流路线的实操细节、参数含义、踩坑点全部摊开。不管你是刚接触安卓打包的新手还是已经打过几十个包、但每次查指纹还要翻旧笔记的老手下面这些内容都能直接拿去用甚至可以当成一份放在手边的操作手册。1. 先搞清楚签名指纹到底是个什么东西1.1 证书指纹的本质一串从公钥里算出来的摘要安卓应用的签名机制底层用的是一套非对称加密体系。你创建 keystore 的时候工具会生成一对密钥私钥留在 keystore 文件里由密码保护公钥则被打包进一张自签名证书跟着 APK 一起发布出去。安装的时候系统会校验包内证书与包内容是否匹配升级的时候还会比对新旧包的证书是否是同一张不一致就直接拒绝安装。而所谓应用签名业务上经常指的其实不是签名本身而是这张证书的指纹。指纹的计算方式很朴素把证书的二进制内容丢进哈希算法得到一个固定长度的摘要值。用 MD5 算就是 32 位十六进制用 SHA-1 算就是 40 位用 SHA-256 算就是 64 位。哈希的单向性保证了别人没法从指纹反推出证书内容但它又是确定性的——同一张证书无论你算多少次、在哪台机器上算结果永远一致。这就是为什么第三方平台敢让你手工填指纹它只要拿你填的值和实际运行时读取到的证书算出来的值做比对就能确认这个包确实是这个开发者签的。这里有个常见的误解需要澄清指纹不是密钥泄露指纹不等于泄露私钥。指纹只是证书的摘要公开出去是安全的你在任何平台填 SHA1 都不会导致 keystore 被破解。真正要命的是 keystore 文件和它的两个密码那才是命根子。我见过有团队把 keystore 直接提交到公开代码仓库里密码写在 gradle.properties 里一起提交这种事故的后果比填错指纹严重一百倍。1.2 为什么平台一会儿要 MD5 一会儿要 SHA1这个问题问的人最多答案其实很现实历史包袱。早期的安卓生态里很多平台在服务端做校验时选用的是 MD5因为它短、计算快、数据库索引友好后来安全圈对 MD5 的碰撞攻击研究越来越成熟行业普遍认为它不再适合做安全校验于是新一代码平台改用 SHA-1再往后 SHA-1 也被认为不够安全SHA-256 成为推荐标准。但平台不会因为算法升级就把老字段删掉因为存量应用还在用老值。所以你经常看到注册页面上一排输入框MD5、SHA1、SHA256 全都要填或者至少要填其中两个。也有平台做得比较聪明只让你填一个 SHA1服务端自己去转换比对但这类属于少数。落到实操上最稳妥的策略是一次性把三个值全部导出并存档哪个平台要哪个就填哪个省得反复操作。很多团队的内部文档里会有一张表记录每个环境、每个签名对应的三种指纹这个习惯非常好后面我会讲怎么用脚本自动生成这张表。1.3 四种获取路径的全景对比在动手之前先看清楚有几条路可走各自的适用场景和局限在哪里。获取方式输入物能拿到的值适用场景主要限制keytool -list -vkeystore/jks 文件MD5、SHA1、SHA256手上有签名文件最常见需要文件路径与两个密码Gradle signingReport工程源码SHA1、SHA256版本差异开发期快速自查需要能编译工程apksigner verify已打包的 APK/AABMD5、SHA1、SHA256只有安装包、无签名文件需要 build-tools 环境运行时反射读取已安装的应用三者皆可线上排查、验证一致性需要改代码、出包四条路的可靠性排序我个人经验是手上有 keystore 就用 keytool这是最权威的源头工程能编译就顺手跑 signingReport快只有 APK 就用 apksigner需要验证线上包和本地签是否一致才动用运行时读取。有一点必须提前说清楚Gradle 的 signingReport 在不同版本的 Android Gradle Plugin 下输出内容并不完全一致早期版本会打印 MD5较新的版本一般只给 SHA-1 和 SHA-256。所以如果你的工程跑完发现没有 MD5别以为是命令敲错了是工具改了输出。这种情况要么退回 keytool要么自己拿 SHA-1 手工换算——当然换算不了哈希之间不存在互相推导关系只能回去找源文件。2. 动手之前必须先弄明白的几个概念2.1 debug 签名和 release 签名完全是两回事几乎每个新手都在这里栽过跟头在 Android Studio 里点绿三角跑起来一切正常接了某个 SDK 也能用于是把当时从 signingReport 里抄下来的 SHA1 填到了后台。等到打正式包发给测试功能全挂了。原因很简单Android Studio 在本地调试时用的是自动生成的 debug keystore位置通常在用户目录下的.android/debug.keystore别名固定是androiddebugkey密码固定是android有效期一般只有 30 年有的版本是 365 天具体看生成时的参数。这个文件和你的正式签名文件毫无关系指纹自然也不一样。而正式发布的包必须用你自己创建的 release keystore 签名指纹是另一套。所以填后台之前先问自己一句这个后台配置对应的是哪个环境的包如果测试同学用的是 debug 包那就要填 debug 的指纹如果直接上 release 包那就填 release 的。很多平台允许你填多个指纹那就两套都填上省得来回改。我在项目里一般会准备一份对照表环境签名文件典型用途debug~/.android/debug.keystore本地调试、内部联调release项目内的 xxx.jks 或 CI 密钥库应用市场发布、正式环境测试专用test.jks预发环境、灰度验证三套签名的指纹各不相同管理上必须分清楚混用是事故高发区。2.2 keystore、alias、密码三者的关系别搞反一个 keystore 文件相当于一个保险箱里面可以存放多个条目每个条目就是一个密钥对加证书用别名alias来区分。所以要定位到具体的证书你需要三样东西keystore 文件路径、别名、以及密码。密码这块还要再细分一个是storepass密钥库口令用来打开整个保险箱另一个是keypass密钥口令用来保护单个条目的私钥。在老式的 JKS 格式里这两个密码可以是不同的但从 JDK 9 开始默认的密钥库格式变成了 PKCS12而 PKCS12 在设计上不支持条目级独立密码两个密码必须相同。这个变化带来的坑非常典型你在 JDK 8 时代建的 JKS 文件storepass 和 keypass 设得不一样升级到 JDK 11 之后再用 keytool 操作输入不同的 keypass 就会报Keystore was tampered with, or password was incorrect或者类似Cannot recover key的异常。遇到这种报错先别怀疑文件损坏大概率就是密码格式的问题。解决办法是显式指定-storetype JKS或者干脆把密钥库迁移成 PKCS12keytool -importkeystore \ -srckeystore old.jks -srcstoretype JKS -srcstorepass 旧密码 \ -destkeystore new.p12 -deststoretype PKCS12 -deststorepass 新密码迁移之前务必先备份原文件这一步没有后悔药。2.3 确认你的 keytool 到底能不能用keytool 是 JDK 自带的工具不需要额外安装。Android Studio 从 4.x 开始内置了 JetBrains Runtime其中就带了一份 JDK但它在各个版本里的路径不固定直接去命令行敲keytool有时候会提示找不到命令。判断方法很简单开一个终端执行keytool -help能打印出帮助信息就说明 PATH 里有可用的 keytool。如果提示 command not found有两个选择一是把 Android Studio 内置 JDK 的 bin 目录加进环境变量路径大致是Android Studio 安装目录/jbr/binmacOS 下在Contents/jbr/Contents/Home/bin二是干脆装一个独立的 JDK我个人更推荐后者版本可控不容易被 IDE 升级带偏。顺便记一下环境变量的两个关键点。Android SDK 的路径由ANDROID_HOME或ANDROID_SDK_ROOT指向build-tools 就在它下面的build-tools/版本号/里apksigner 就在那儿。查一下当前装了哪些版本ls $ANDROID_HOME/build-tools输出会有好几个版本号目录比如 33.0.1、34.0.0 之类。apksigner 请优先用最新版本的那个因为高版本能识别 v3、v4 签名方案低版本遇到新方案可能直接报错或者识别不全。3. keytool 实操从 keystore 里读出三种指纹3.1 命令逐个参数拆开讲最核心的一条命令长这样keytool -list -v \ -keystore release.jks \ -alias mykey \ -storepass 你的库密码 \ -keypass 你的条目密码四个参数各司其职-list表示列出条目-v是 verbose 的缩写意思是输出详细信息——没有-v你只能看到条目的 SHA1 指纹一行加上-v才会打印完整证书链和三种哈希。这一点很多人不知道抄命令的时候把-v丢了然后抱怨输出信息太少。-alias指定要查的条目。如果你的 keystore 里只有一个条目这个参数其实可以省略keytool 会把所有条目都列出来。但只要保险箱里超过一个密钥就必须指定不然你分不清哪个是哪个。-storepass和-keypass就是前面说的两个密码。省掉它们也可以keytool 会交互式提示你输入输入时字符不回显。在 CI 或者脚本里必须用参数形式传人机交互在自动化流程里是走不通的。执行成功后的输出大概是这样内容为示意Keystore type: PKCS12 Keystore provider: SUN Your keystore contains 1 entry Alias name: mykey Creation date: 2023-08-14 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CNunknown, OUunknown, Ounknown, Lunknown, STunknown, Cunknown Issuer: CNunknown, OUunknown, Ounknown, Lunknown, STunknown, Cunknown Serial number: 5f3a... Valid from: Mon Aug 14 10:22:31 CST 2023 until: Fri Aug 07 10:22:31 CST 2053 Certificate fingerprints: SHA1: 3A:7B:...:9C SHA256: 91:0F:...:E4 Signature algorithm name: SHA256withRSA Subject Public Key Algorithm: 2048-bit RSA key注意看Certificate fingerprints这一段新版 JDK 默认只打印 SHA1 和 SHA256MD5 那行被去掉了因为 JDK 出于安全考虑屏蔽了弱哈希算法的输出。这是很多人在 JDK 11 及以上环境里查不到 MD5 的直接原因。要拿到 MD5有两个办法。一是临时启动参数里放开限制keytool -J-Djdk.certpath.disabledAlgorithms -list -v -keystore release.jks -alias mykey-J前缀表示把后面的参数透传给 JVM把禁用算法列表清空MD5 就会重新出现在输出里。二是用 apksigner 反向从 APK 里读它输出的证书摘要里是带 MD5 的。我个人倾向第二种不用改 JVM 参数更干净。3.2 密码的几种传法与实际取舍刚才提到密码可以用参数传也可以交互输入。这中间还有几种变体值得说。参数直传-storepass 123456。优点是脚本友好缺点是密码会留在 shell history 里多人共用的机器上有泄露风险。如果非要用记得在命令前加一个空格部分 shell 需要配置HISTCONTROLignorespace才生效或者用完清历史。从文件读keytool 本身不支持-storepass:file这种写法但你可以用 shell 的能力绕过比如把密码写进一个只有自己能读的文件用变量传进去STOREPASS$(cat ~/.secret/store.pass) keytool -list -v -keystore release.jks -alias mykey -storepass $STOREPASS交互输入直接省掉-storepass等提示。这种方式最安全适合一次性手工操作。缺点是如果密码输错keytool 会给一个相当含糊的报错容易被误导成文件问题。关于报错提前给几个对照报错信息真实原因处理方向Keystore was tampered with, or password was incorrect库密码错误或 JKS 用了不同的 keypass先确认库密码再确认密钥库类型alias does not exist别名拼错或大小写不符先不带 alias 列出所有条目Cannot recover keykeypass 与 storepass 不一致PKCS12统一密码或指定 JKS 类型Invalid keystore format文件损坏或不是密钥库检查文件大小重新获取这几条几乎覆盖了 keytool 查指纹时 90% 的报错。3.3 路径与引号跨平台最容易翻车的地方Windows 下路径带反斜杠在命令行里需要转义或改成正斜杠。我一般直接改成D:/keys/release.jks这种形式两种平台都能识别省事。如果路径里有空格整个路径必须用双引号包起来否则会被拆成多个参数keytool -list -v -keystore D:/My Keys/release.jks -alias mykeymacOS 和 Linux 下相对路径的解析基准是当前工作目录如果你在 Android Studio 的 Terminal 里执行基准通常是项目根目录而不是模块目录。这一点经常导致我明明看到文件在这儿却报找不到。稳妥做法是先把文件拖进终端拿到绝对路径再拼命令。还有个小细节文件名后缀。.jks和.keystore都只是习惯命名keytool 并不依赖后缀判断格式它读文件头。所以拿到一个名叫sign.txt的密钥库也不用惊讶改成正确的名字再操作就行。3.4 输出结果怎么记录才不返工拿到指纹之后随手记在聊天记录里是最容易丢失的做法过两个月绝对找不到。我自己的习惯是建一个keystore-info.md放在安全的地方不是代码仓库内容大概是### release 签名2023-08-14 创建 文件release.jks 别名mykey 有效期至2053-08-07 MD5: XX:XX:... SHA1: XX:XX:... SHA256: XX:XX:... 存放位置加密压缩包 离线备份这里要强调一点这个文档里不要写密码。密码单独存在密码管理器里文档只记录指纹和元信息。指纹泄露无害密码泄露致命两者必须物理隔离。4. Android Studio 里更省事的两条捷径4.1 一条 Gradle 任务搞定所有变体工程能编译的情况下最快的办法是在项目根目录执行./gradlew signingReportWindows 下用gradlew.bat signingReport。执行完会输出类似这样的结构 Task :app:signingReport Variant: debug Config: debug Store: /Users/xxx/.android/debug.keystore Alias: AndroidDebugKey MD5: ... SHA1: 5E:8F:... SHA-256: ... Valid until: ... Variant: release Config: release Store: /Users/xxx/keys/release.jks Alias: mykey SHA1: 3A:7B:... SHA-256: 91:0F:...这个输出最舒服的地方在于它把每个变体、每个渠道的签名配置全部列出来了。如果你的项目配置了多个 productFlavor每个 flavor 用了不同的签名文件这里能一次性看全不用逐个去翻 build.gradle。需要注意的是这个任务读的是 Gradle 里signingConfigs的配置。如果你的密码是通过环境变量或者local.properties注入的记得先在本地把这些值补上否则任务会失败。另外如果某个变体的签名配置是条件性的比如if (project.hasProperty(release))里才设置在没有该属性的情况下输出里会显示Store: null这时候查不到指纹是正常的加上参数重跑即可。4.2 图形界面也能看但别指望它给你全部Android Studio 的菜单Build Generate Signed Bundle / APK走到选择密钥库的那一步界面上会显示一些证书信息。这个路径的好处是纯图形适合完全不想碰命令行的人。但它的短板很明显显示的信息有限通常只有一些基本的摘要而且必须走完整个向导的前几步才能看到纯粹为了查个指纹走一遍打包流程有点绕。我的建议是把它当验证手段而不是获取手段当你从命令行拿到指纹后不确定是不是选对了文件可以用这个界面快速确认一下别名和有效期是否对得上。4.3 运行时读取验证线上包是否用的是同一张证书有些时候你没法确定线上跑的包用的是哪套签名比如接手别人的项目、或者从某处拿到一个 APK 但没有任何配套文件。这时候最直接的办法是在代码里读出来。下面是一段 Kotlin 版本的工具函数逻辑是拿到应用自身的签名信息对证书字节做三种哈希格式化成冒号分隔的大写十六进制。import android.content.Context import android.content.pm.PackageManager import android.os.Build import java.security.MessageDigest object SignatureUtil { fun printAll(context: Context) { val pm context.packageManager val pkg context.packageName val signatures if (Build.VERSION.SDK_INT Build.VERSION_CODES.P) { val info pm.getPackageInfo(pkg, PackageManager.GET_SIGNING_CERTIFICATES) val signingInfo info.signingInfo ?: return if (signingInfo.hasMultipleSigners()) { signingInfo.apkContentsSigners } else { signingInfo.signingCertificateHistory } } else { Suppress(DEPRECATION) val info pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES) Suppress(DEPRECATION) info.signatures ?: return } signatures.forEach { sig - val cert sig.toByteArray() println(MD5 - ${digest(cert, MD5)}) println(SHA1 - ${digest(cert, SHA1)}) println(SHA256 - ${digest(cert, SHA-256)}) } } private fun digest(bytes: ByteArray, algorithm: String): String { val md MessageDigest.getInstance(algorithm) val result md.digest(bytes) return result.joinToString(:) { %02X.format(it) } } }几个关键点解释一下。GET_SIGNING_CERTIFICATES是 API 28 之后引入的新接口取代了老的GET_SIGNATURES因为老接口在多签名者、轮换密钥等场景下语义不清。hasMultipleSigners()判断是否存在多个签名者多个签名者用于联合调试一类的场景返回 true 走apkContentsSigners否则走signingCertificateHistory这个历史列表里第一个是最新证书后面的可能是轮换前的旧证书。如果是做校验用途通常应该取历史里的最后一个也就是最初的证书这样才能保证密钥轮换前后算出来的指纹一致。%02X.format(it)里的it是 Byte转成整数需要注意负数问题——这里String.format会自动处理为两位十六进制结果正确。如果你自己写Integer.toHexString(byte.toInt() and 0xFF)记得一定要and 0xFF否则负数会输出一长串 F。不要把这段代码留在正式发布包里。它本身无害但暴露指纹信息给逆向者提供了一点便利。做法是包在if (BuildConfig.DEBUG)里或者只在内部排查包里加上。5. apksigner 实操从安装包反查签名5.1 工具在哪怎么调起来apksigner 是 build-tools 里的一个脚本Windows 下叫apksigner.batmacOS/Linux 下是apksigner这个可执行文件。完整路径形如$ANDROID_HOME/build-tools/34.0.0/apksigner如果你不想每次都敲全路径有两种方式。一是把某个版本的 build-tools 目录加进 PATH二是在 shell 里定义个别名alias apksigner$ANDROID_HOME/build-tools/34.0.0/apksigner注意别把多个版本的 build-tools 同时放进 PATH容易出现调用到旧版本的情况遇到莫名其妙的报错先which apksigner看一眼实际路径。5.2 verify --print-certs 的完整用法核心命令只有一行apksigner verify --print-certs app-release.apk输出大致是Verifies Verified using v1 scheme (JAR signing): false Verified using v2 scheme (APK Signature Scheme v2): true Verified using v3 scheme (APK Signature Scheme v3): true Number of signers: 1 Signer #1 certificate DN: CNunknown, Ounknown Signer #1 certificate SHA-256 digest: 910f... Signer #1 certificate SHA-1 digest: 3a7b... Signer #1 certificate MD5 digest: c4e9... Signer #1 key algorithm: RSA Signer #1 key size (bits): 2048 Signer #1 certificate validity: 2023-08-14 to 2053-08-07这里的信息量比 keytool 还大尤其是上面那三行Verified using直接告诉你这个包用了哪几套签名方案。现代应用一般 v2 和 v3 都是 truev1 可能是 false——因为从 Android 7.0 开始系统支持 v2 方案从 AGP 某个版本起默认的最小 SDK 如果大于 24就会关掉 v1 以减少包体积。这不影响安装但如果你的应用要兼容 Android 7 以下的设备v1 必须是 true否则老系统装不上。输出里的三行 digest 就是你要的 MD5、SHA1、SHA256但注意格式apksigner 输出的是不带冒号的小写十六进制串。而绝大多数第三方平台的后台输入框要求的是带冒号的大写形式或者至少是带冒号的。直接复制粘贴过去校验必然失败。转换规则很简单原始格式目标格式处理方式910f2a3b...无冒号小写91:0F:2A:3B:...每两位插入冒号转大写91:0F:2A:3B:...带冒号大写910f2a3b...去冒号转小写手工转 64 位的 SHA256 显然不现实用命令处理。macOS/Linux 下echo 910f2a3b... | tr a-f A-F | sed s/../:/g; s/:$//Windows PowerShell$s 910f2a3b... ($s -split (..) | Where-Object { $_ } ) -join : | ForEach-Object { $_.ToUpper() }我一般会在脚本里封装一个函数输入无冒号串输出标准格式后面讲一键导出脚本的时候会用到。5.3 apksigner 和 jarsigner 该怎么选有些老教程还在用jarsigner -verify -verbose -certs app.apk来查签名。这条命令能跑但只能验证 v1 方案JAR 签名如果 APK 只用了 v2/v3 方案现代应用很常见jarsigner 会告诉你jar 未签名让人一头雾水。另一条老命令是keytool -printcert -jarfile app.apk同样只能读 v1 签名块里的证书。这两条命令在 2018 年以前基本够用现在只能算补充手段。结论很明确查 APK 签名首选 apksigner。它的实现和系统安装时的校验逻辑一致能覆盖 v1 到 v4 全部方案输出的信息也最全。只有当你的 build-tools 版本实在太旧、根本没有 apksigner 时才退回去用 jarsigner而那种情况本身就说明环境该升级了。还有一个使用细节值得说apksigner verify默认会做完整性校验如果 APK 被二次修改过比如某些加固工具处理过可能会报DOES NOT VERIFY错误并给出具体原因。这种情况下加--min-sdk-version和--max-sdk-version指定范围或者用--print-certs单独看证书能绕过一部分校验逻辑。证书信息本身还是能读出来的。5.4 一次性导出全部信息的脚本前面提到过最好有一条命令能把三种指纹全部规范化输出。下面这段 Bash 脚本我用得最多输入一个 APK 路径输出一张整理好的表#!/usr/bin/env bash APK$1 APKSIGNER$ANDROID_HOME/build-tools/34.0.0/apksigner raw$($APKSIGNER verify --print-certs $APK) format() { echo $1 | tr a-f A-F | sed s/../:/g; s/:$// } md5$(echo $raw | grep MD5 digest | awk -F: {print $2}) sha1$(echo $raw | grep SHA-1 digest | awk -F: {print $2}) sha256$(echo $raw| grep SHA-256 digest| awk -F: {print $2}) echo MD5 : $(format $md5) echo SHA1 : $(format $sha1) echo SHA256 : $(format $sha256)在 CI 里出包之后顺手跑一下把结果写进构建产物旁边的signature.txt交付的时候一起给出去对甲方和测试同学都非常友好。6. 常见问题与排查实录6.1 后台填了指纹SDK 还是报签名校验失败这是最高频的问题八成不是指纹抄错了而是你填的指纹和你实际跑的包不是一套签名。排查顺序应该是这样的第一步确认手上这个安装包是用什么签的。直接对 APK 跑apksigner verify --print-certs拿到真实指纹。第二步和你填到后台的值逐字符比对。注意括号、冒号、大小写。有些平台输入框不校验格式你少个冒号它照样保存运行时比对就挂了。第三步确认后台配置的是哪个环境的密钥。不少平台的开发者后台把 debug 和 release 的配置分开放你在 debug 那一栏填了 release 的指纹测试同学用 debug 包自然对不上。第四步考虑缓存问题。有些 SDK 会把校验结果缓存到本地改完配置需要清应用数据或者重装才能生效。我遇到过一次特别隐蔽的后台要求填 SHA1团队里有人把 SHA256 前 40 位截出来填了进去长度对得上格式也对得上就是值不对。这种只能靠逐字符核对没有捷径。6.2 找不到 keystore 文件怎么办项目交接时最常见。先别慌按这个顺序找先看项目里有没有keystore.properties、signing.properties、local.properties这类文件build.gradle的signingConfigs块里通常会引用其中的路径。然后去持续集成平台的配置里找如果有签名文件常常以变量形式存在那里。再问一圈历史提交记录里有没有人提交过虽然不建议但现实里确实存在。如果全都找不到那就只剩下两条路一是从上架的应用市场下载正式版 APK用 apksigner 读出证书指纹至少能知道指纹是什么但私钥拿不回来无法再用这个签名出新包二是启用密钥轮换机制需要 Android 9 以上和对应的市场支持且限制很多。所以最根本的解决方案还是那句老话keystore 必须有多份离线备份。6.3 常见问题速查表把上面这些和实践中遇到的其他问题汇总成一张表出问题时直接对照现象大概率原因处理动作keytool 输出没有 MD5JDK 9 禁用弱算法加 -J 参数放开或用 apksignersigningReport 没有 MD5AGP 版本输出项变化改用 keytool 或 apksigner报 Keystore was tampered with库密码错 / PKCS12 密码不一致确认库密码或加 -storetype JKS报 alias does not exist别名错误去掉 -alias 先列出全部条目apksigner 提示命令不存在未配置 build-tools 路径用绝对路径调用后台校验失败但指纹看起来对大小写 / 冒号格式不符按目标格式重新转换debug 包能用 release 包不能用两套签名指纹混用分别导出两套都配置jarsigner 说未签名只验证 v1 方案改用 apksigner换电脑后打包报签名错误keystore 路径是绝对路径改为相对路径或环境变量6.4 关于签名方案版本的两个坑第一个坑v2/v3 签名不影响指纹值但影响能否被读到。有的 APK 只用了 v2 方案keytool -printcert -jarfile读不出证书会报Not a signed jar file。这时候别怀疑包坏了换 apksigner 就行。第二个坑签名方案降级会导致安装失败。从 Android 11 开始系统要求 targetSdk 30 以上的应用必须使用 v2 及以上方案签名。如果你的打包脚本里手动指定了只启用 v1新设备上是装不上的报错信息通常是INSTALL_PARSE_FAILED_NO_CERTIFICATES或类似的。检查signingConfigs里enableV1Signing、enableV2Signing这些开关除非兼容极老设备否则都应该让 v2/v3 保持开启。7. 几个只有踩过坑才知道的实操经验7.1 把指纹导出做成脚本别靠人肉团队里只要超过两个人就一定会出现我填的到底是不是最新那套的困惑。解决办法是把导出过程自动化。我在项目里加了一个 Gradle 任务挂到assembleRelease之后tasks.register(dumpSignatures) { doLast { android.applicationVariants.all { variant - if (variant.buildType.name release) { def fp variant.signingConfig?.storeFile if (fp ! null fp.exists()) { println 查签名${fp.absolutePath} } } } } }更彻底的做法是在 CI 里执行 keytool 命令把结果作为构建产物的一部分归档。这样每一次构建都有对应的指纹记录出现问题时可以回溯这个包当时是什么签名。这个习惯带来的收益是长期的当你需要为某个历史版本排查签名不一致的问题时有据可查比凭记忆猜要靠谱得多。7.2 keystore 备份的正确姿势这块要说重话。丢失 release keystore 意味着这个应用永远无法更新只能重新上架一个新包名用户全部流失。所以备份策略必须严肃对待。我的做法是三份两处本地加密压缩包一份离线存储介质一份团队保险柜或密码管理器里存一份恢复说明。压缩包密码和 keystore 密码分开保管由不同的人持有。每次创建新签名文件之后立即做完整备份并验证备份可解压、可用 keytool 正常读取——备份不做验证等于没备份。还有一个细节备份的时候连同keystore-info.md一起存里面记录别名、有效期、创建时间。光有文件没有别名几年后照样打不开。7.3 多地多签名场景下的指纹管理中大型项目常常有多个签名国内渠道一套海外一套企业内部测试一套。每套对应不同的指纹注册在不同平台的后台。这种情况靠人工记忆必然出错建议建一张配置表放在内部文档里渠道签名别名对应后台备注主渠道release_cn推送、地图、支付后台三个指纹全填海外渠道release_overseas海外服务后台需单独申请内部测试test_internal测试环境后台不建议填生产这张表要跟着版本走每次新增渠道就更新。看起来麻烦但比在发布前夜发现签名配错要省心一万倍。最后分享一个小技巧如果你需要频繁在多个签名之间切换验证可以在~/.bashrc或者~/.zshrc里定义几个快捷函数比如sig-release、sig-debug分别指向不同的 keystore 和别名输入一个词就能打印三种指纹。用久了你会发现把重复劳动交给脚本才是避免低级错误的根本办法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑