搞 Android 开发的,几乎没人能绕开“应用签名”这四个字。尤其是当你去某个开放平台申请接入、或者手里拿着一堆不同渠道包需要核对身份的时候,有没有签名、拿到的签名对不对,直接决定你这一天是顺顺利利还是从早调到晚。前阵子帮一个同事排查 SDK 登录失败,最后就是签名对不上,平台后台报“应用签名校验失败”,查了一圈才发现他对着 debug 包拉了签名,线上 release 包的签名压根不是那回事。
这种坑踩过的人不在少数。所以这期我直接把“Android Studio 获取 app 应用签名”这件事儿从头到尾拆开来讲,把平时最常用的、能直接抄走用的几种方法全给你列清楚,顺带把背后容易踩的细节一起理一遍。
1. 先说清楚:应用签名到底是“什么”又是“给谁用的”
你要搞懂怎么获取签名,得先知道签名的本质是什么。简单说,签名就是应用包(APK/AAB)的身份证,用你手里的 keystore(签名文件,通常后缀是 .jks 或 .keystore)配合密钥生成出来的一组指纹信息,包含 MD5、SHA1、SHA256 这几类。系统在安装应用时会校验这个指纹,第三方 SDK 平台也会校验。
日常开发中你需要用到签名的场景主要集中在这么几种:
- 接入第三方 SDK 服务时,对方平台要求上传包名和“应用签名”,尤其是支付、地图、登录、推送这类能力,基本是必填项。
- 应用做多渠道打包、热修复、插件化时,校验签名是否一致。
- 排查上线后安装失败,或者覆盖安装被拒时,需要确认签名算法和指纹是否符合预期。
很多没经验的朋友容易有误区,以为同一个项目所有包签名都一样。实际上 debug 包默认使用 debug.keystore,release 包必须指定自己的正式签名文件,这俩的指纹完全是两码事。你拿 debug 签名去填平台,再把 release 包传上去,必然校验失败。
还有一点你得清楚,获取签名不是为了“看”,而是为了“填到该填的地方”。所以后面每个方法我都会顺便讲清楚,拿到手的字符串应该怎么处理才能用。
2. Android Studio 图形化查看签名:signingReport 任务
这是最省事的方法,不需要装额外工具,也不需要你记得命令行参数。打开 Android Studio,右下角找到 Gradle 面板(没有的话在 View > Tool Windows > Gradle 里找),展开你的项目模块,找到Tasks再找到android分组,里面有一个叫signingReport的任务。双击跑一下,输出会直接列出当前模块所有 build variant 对应的签名信息。
输出内容大概是这个样子:
Variant: debug Config: debug Store: /Users/yourname/.android/debug.keystore Alias: AndroidDebugKey MD5: 6B:62:3F:... SHA1: 3A:41:... SHA-256: 7E:... Variant: release Config: release Store: /path/to/your-release.keystore Alias: release MD5: ... SHA1: ... SHA-256: ...这个方式最大的好处是可视化,不用记命令,而且把所有 variant 的信息一次性列全。我一般第一次接手项目时就会先跑一次 signingReport,几秒钟就能把项目里 debug/release 的指纹底细摸清,后续核对的时候心里有数。
但有几个注意事项:
- 前提是模块里的 signingConfigs 有配置。如果项目里没有配置签名,release 那栏很可能显示
Config: release但下面没有指纹信息,只显示一行Signing certificate后面跟着CN=Unknown。这不代表系统没有签名,而是 Gradle 层面没读到 signingConfig。这种情况下更推荐用命令行方式(下面会讲)。 - 输出内容可能滚动很长。模块多、多渠道多的时候,要自己找准对应 flavor 的输出,别复制串了。
- 个别新版本 Android Studio 里 Gradle 面板默认不展开 Task 列表,对着“android”双击展开就行,找不到就点一下刷新按钮。
如果你只用 AS 自带这种方式,日常填平台签名已经够了。但它有个限制:一旦你手里没有项目工程、只有一个 APK 文件,这套就没法用了。这种情况要往下看命令行和 APK 解析的方式。
3. 命令行武器:keytool 获取签名信息
keytool 是 JDK 自带的密钥和证书管理工具。只要你本机装过 JDK,命令行里基本可以直接用。这是我认为最通用、也是最快的方法,适用面比 AS 图形化方式广得多,不管是拿到 jks 文件、debug.keystore 文件,还是想顺便核对密码对不对,都靠它。
先看最核心的命令:
keytool -list -v -keystore /你的绝对路径/xxx.jks -alias 别名 -storepass 密码解释一下关键参数:
-list表示要列出证书信息。-v表示以详细模式输出,否则默认只会输出指纹的简短列表,信息太少。-keystore后面跟签名文件的绝对路径,尽量写绝对路径,避免当前目录不对导致No such file or directory。-alias指定别名,不是必填,但建议带上。一个 keystore 里可以放多个证书,不带别名会把所有证书都列出来,带别名更精准。-storepass是 keystore 的密码,不写的话命令会提示你交互输入,安全性高一点,但脚本化执行时必须写。
如果不想填别名,也可以直接拉列表:
keytool -list -v -keystore /你的绝对路径/xxx.jks执行后输出中有三块我们最关心:
证书指纹: SHA1: 3A:41:6B:... SHA256: 7E:62:3F:... 签名算法名称: SHA256withRSA 版本: 3等等,这里有个关键区别要先说明:keytool 的输出默认是包含SHA1和SHA256的,但很多第三方平台(比如一些开放平台)让你填的“应用签名”其实是MD5,而且要求去掉冒号、全小写。那么 keytool 能不能直接给你输出 MD5?能,但要换一个不带-v的简短列表格式:
keytool -list -keystore /你的绝对路径/xxx.jks -alias 别名 -storepass 密码这样输出的 MD5 格式是6B:62:3F:...,带冒号且大写。你需要再处理一下:去掉冒号、转成小写,最终类似于6b623f...这样一串 32 位字符。这个点我遇到过太多次,很多人拿到了完整带冒号的 MD5 直接就粘贴进平台,结果平台上显示校验失败,折腾半天才发现是格式问题。
Debug 包的默认签名文件,在 macOS/Linux 是~/.android/debug.keystore,Windows 是C:\Users\你的用户名\.android\debug.keystore,默认别名是androiddebugkey,默认密码是android。所以获取 debug 签名可以直接用这条:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android还有一个很常见的坑:命令行输入keytool提示“不是内部或外部命令”。说明JAVA_HOME没配好,或者说 Java 没安装。解决方式是直接用 Android Studio 内置的 JDK 路径去调用,比如 macOS 上 AS 自带 JBR(JetBrains Runtime)路径,Windows 上也有对应的 jbr 目录,cd 到该目录的 bin 下再执行 keytool。
这种方式我平时用得最多,因为它不依赖项目工程,只要手上有一个签名文件就能干活,而且输出很快。唯一的门槛是要记住命令参数,不过把这条命令存成 alias 或者写进自己的 scripts 目录,一次配置永远复用。
4. 运行时直接拿签名:App 内自查与跨包校验
第三种场景稍微特殊一点:你需要验证当前手机上正在运行的这个 App,它安装到设备上的包签名到底是什么,而不是去看构建产物的文件。这种场景最典型的就是接入了某些统计平台,需要上报本机签名做反作弊;或者是多渠道归因排查时,要确认线上包是不是真的由某个特定 keystore 签出来的。
这种时候 Java/Kotlin 代码直接读包管理器的信息,比什么都准。核心逻辑是:通过PackageManager拿到PackageInfo,再去拿签名数组,然后用MessageDigest分别算 MD5、SHA1、SHA256。
直接上 Kotlin 代码:
import android.content.pm.PackageManager import java.security.MessageDigest import java.security.cert.CertificateFactory import java.io.ByteArrayInputStream fun getAppSignature() { val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) } else { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) } // 兼容新老 API val signatureByteArray = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { packageInfo.signingInfo ?.apkContentsSigners ?.get(0) ?.toByteArray() } else { packageInfo.signatures?.get(0)?.toByteArray() } val md5 = MessageDigest.getInstance("MD5").digest(signatureByteArray) val sha1 = MessageDigest.getInstance("SHA1").digest(signatureByteArray) val sha256 = MessageDigest.getInstance("SHA256").digest(signatureByteArray) // 转成十六进制带冒号形式 println("MD5: " + md5.joinToString(":") { "%02x".format(it) }) println("SHA1: " + sha1.joinToString(":") { "%02x".format(it) }) println("SHA256:" + sha256.joinToString(":") { "%02x".format(it) }) }这里特别说一下,Android 9(API 28)开始,PackageManager.GET_SIGNATURES这个常量和方法虽然还能用,但是已经标记为过时,系统会建议你用GET_SIGNING_CERTIFICATES配合signingInfo来获取。但从兼容性角度,你仍然可以在低版本分支里保留旧写法,这在写 SDK 的时候非常常见。实际项目里我一般会把这套逻辑做成一个单独工具类,既方便内部调用,也方便直接打印 Log 查看。
需要注意,这种方式拿到的信息来自安装包本身的签名块,只要 App 是从正规渠道安装的,签名信息就是真实的。即便你本地构建一个包出来,拿 keytool 查文件和运行时查签名,出来的指纹一定是一致的。这样反过来也可以做本地校验:把命令行查到的指纹和运行时查到的指纹对一下,能不能对上,一眼就知道签名到底用没用对。
另外,如果你是想在build.gradle里直接写 Task 打印签名,理论上也能实现,但复杂度比前面几种高不少,而且灵活性不如直接看命令行,这里就不展开了。日常开发用上面这个工具类足够。
5. 手里只有 APK 没有签名文件:从安装包提取签名
刚才说的都是拿着签名文件或者项目工程就能查。现实里还有一种很常见的情况:项目交接、客户甩过来一个 APK,又或者你是测试方,需要核对线上 APK 和源仓库里的签名是否一致。这时候 keystore 文件不一定在你手上,但你依然有办法查出这个 APK 的签名。
最正统的查法是使用 Android SDK 构建工具里的apksigner工具,它在build-tools目录下,比如sdk/build-tools/34.0.0/apksigner。直接执行:
apksigner verify --print-certs app-release.apk这个命令会打印非常完整的信息,包括签名算法版本(v1/v2/v3)、证书指纹的 SHA-256、SHA-1、MD5 等,以及证书 DN 信息。这个方法我最推荐,因为它能顺带看出这个 APK 用的是 v1 还是 v2 签名,对于排查低版本兼容性很有用。
如果你不习惯命令行,也可以直接把 APK 当成 zip 解压,用在线证书查看器去打开META-INF/CERT.RSA这个文件。但这条路有点绕,从效率角度我基本不用。还有一类第三方工具比如 APK 信息查看器,虽然可视化程度高一点,但都需要安装外部软件,没必要专门为了查个签名多装工具。
走 apksigner 这个方式需要注意一点:APK 的签名信息是构建时写进包里的,它不像 keystore 文件那样可以反复查,APK 一旦构建完,你就没法再改变它的签名。所以如果你需要比较两个包是不是同一个 keystore 签的,就把两个 APK 都跑一条 apksigner 命令,对比 SHA256 是否一致,这是最可靠的判断手段。
另外提醒一句:如果是 AAB(Android App Bundle)格式的话,apksigner 不能直接解析 AAB,但你可以用bundletool先构建出 APK 再来验签。不过实际开发中更容易出现的是“商店渠道给的是 AAB,测试装的是商店下载的 APK”,所以这个对比链条反而常被用到,不要觉得 AAB 就彻底和签名无关。
6. 签名获取时的常见错误与避坑实操
技术方法本身不复杂,但我在实际开发里见过太多因为“签名没对上”导致的问题,所以专门把这部分单拎出来写一节。一张表看明白:
| 常见现象 | 根本原因 | 解决办法 |
|---|---|---|
| 平台提示“应用签名校验失败” | 填了 debug 签名,线上包是 release 签名 | 确认线上包用的 keystore,重新获取 release 指纹填进去 |
| 平台要求填 MD5,自己填了 SHA1 | 没看平台字段要求 | 看清楚要求,keytool 不带-v或者 apksigner 输出里找 MD5 |
| 填了带冒号大写的 MD5,平台说是错的 | 平台要求去掉冒号小写格式 | 统一做字符串处理:去冒号、转小写 |
| 两个渠道包指纹对不上 | 每个渠道用了不同 keystore 签名 | 检查打包脚本,统一签名配置 |
keytool命令找不到 | JDK 环境没配置 | 用 Android Studio 自带 jbr 目录下的 keytool |
| 覆盖安装提示“签名不一致” | debug 包覆盖已安装的 release 包 | 测试机先用 release 包卸载旧版本,再装新包 |
| signingReport 里看不到 release 签名 | 模块里 signingConfig 没配置完整 | 改用 keytool 命令行从 keystore 文件直接查 |
| 安卓 7.0 以下安装 v2 签名包失败 | v1/v2 签名兼容性问题 | 构建时同时开启 v1 与 v2 签名,或至少保证 v1 存在 |
这里重点说几个容易被忽略的点。
第一个是debug 和 release 签名混用的问题。有的人图省事,直接把 release 签名配置到 debug 里,全局都用同一个签名。这样做确实规避了“签名不一致”的问题,但代价是 debug 包占用的存储标识和线上一致,某些内部灰度统计会把 debug 当成线上流量。如果项目还没有正式发版,倒没太大影响;但一旦线上已有大量用户,不建议这么做。我的习惯是 debug 保持默认 debug 签名,release 独立签名,两个互不干扰,靠打包脚本控制。
第二个是MD5 的格式细节。很多平台文档会写“应用签名为 MD5(去除冒号,且为小写)”。keytool 默认输出的 MD5 是大写带冒号,apksigner 输出也是十六进制片段拼接。所以最保险的做法是在提交前统一处理成一个 32 位的小写十六进制串。我自己写过一个简单的脚本,自动从 keytool 输出里抓 MD5 并格式化,避免每次手动改。
第三个是v1/v2/v3 签名算法对校验的影响。部分老平台的校验逻辑停留在 v1 签名读取上,如果你的 APK 只启用了 v2 签名,某些服务端校验可能读不到有效证书,从而报签名异常。现在新的构建工具默认同时启用 v1+v2+v3,除非你手动降级,否则通常是没问题的。但如果你在build.gradle里写了signingConfig自己控制签名启用,那么低版本兼容性的问题就要额外注意。
第四个坑,也是比较隐蔽的一种:同一个 keystore 但别名不同。一个 jks 文件里可能存了好几个证书,你在 Gradle 里配的是别名 A,结果用命令行查的时候用了别名 B,拿到的指纹当然不一样。这种出错往往让人怀疑 keystore 有问题,其实是别名没对上。解决办法很简单,先用不带-alias参数的命令把 keystore 里所有别名都列出来,看清了再定向查。
另外再分享一个对排查很有用的小习惯:把签名信息输出保存到一个固定文件里,比如signature.md,并在每次发版前更新一次。别小看这一步,等你要去开放平台申请能力、或者回答客服“这个包签名是多少”的时候,翻一下文件比重新跑命令快得多,也好核对版本差异。
7. 顺带的经验之谈
这部分不属于某个具体方法,但多花几十秒看完能帮你少走弯路。我实际工作中一旦遇到跟签名相关的问题,第一反应不是去改代码,而是先跑一条 signatureReport 或者 keytool 命令,把当前包的指纹和平台上的记录对齐做对比。因为签名校验这个环节出现差异,绝大多数情况都是配置层面的问题,代码逻辑本身没问题,你自己瞎猜半天不如直接拉数据说话。
如果要在 App 内自检签名,建议不要只打 Log,可以在隐藏的调试页面里把签名指纹显示出来,方便测试人员截图反馈。我参与过的某个项目就是这么做的,排查线上偶发登录失败时,测试直接截图发签名指纹,后端一看就知道是不是包的问题,效率高很多。
还有一点,签名文件(.jks/.keystore)本身属于高度敏感信息,它一旦泄露,别人就能拿着你的签名文件加上你的包名去伪造一个“正版 App”,这对用户和品牌都是灾难。所以任何情况下都不要把签名文件提交到代码仓库,哪怕私有仓库也尽量不要。如果团队协作,签名文件建议放在加密的密码管理工具里,或者由固定的发布负责人保管,通过受控的构建产物分发来保证所有人拿到的是可信的包。项目里曾经出过同事把 jks 连同密码直接推到仓库、离职后密码外流的事情,后来我们花了很大成本才把整个应用签名体系换掉,这个代价是非常大的。
签名获取本身只是一项基础技能,但把它背后的配置、格式、算法和协作管理都理顺了,你才算是真正吃得透。希望这篇内容能帮你把“获取签名”这条链路彻底打通,下一次再碰到签名校验、改包核对之类的需求时,至少能先选对工具、拿对数据、避掉明坑。