rea-reverse

Android 签名 SHA-1 / SHA-256 指纹怎么查:keystore、APK、AAB 实测

发布者 rea-reverse更新于 约 8 分钟读完
本页目录

结论先说:手里是 keystore,就跑 keytool -list -v -keystore your.jks -alias your-alias,复制 SHA1: 和 SHA256: 两行;手里是打好的 APK,就跑 apksigner verify --print-certs app.apk;在 Android 工程里,./gradlew signingReport 会把每个构建变体的指纹都打出来。同一把钥匙,三种方法得到的摘要完全一致。真正容易出错的是别的地方:PKCS#12 格式的 keystore 不给密码就一个指纹都不显示;keytool -printcert -jarfile 会对一个签名完好的新 APK 说 “Not a signed jar file”;apksigner 不认 AAB;Facebook key hash 那条常见的管道命令,别名写错时会把一句报错信息算成哈希交给你。下面每一条都附上我实际跑出来的输出。

只想要结果的话,把 keystore、APK 或 AAB 拖到本站的 签名证书指纹查看器 里即可。它在浏览器本地运行,文件不上传,同时给出 Firebase 要的冒号十六进制、无冒号十六进制,以及 Facebook 要的 Base64 三种格式。

适合谁读:正在接 Firebase、Google 登录、Maps SDK、API 密钥限制、Facebook 登录或 Android App Links 的 Android 开发者,想弄清楚该填哪个指纹、它从哪儿来。

测试环境

以下操作都在 2026-10-20 完成,系统是 macOS 27.0.1,工具版本:

  • Temurin JDK 17.0.18(keytool 用的就是这个 JDK 自带的)
  • Android build-tools 36.1.0(apksigner --version 输出 0.9)
  • Gradle 9.6.1 + Android Gradle Plugin 9.4.1
  • OpenSSL 4.0.3

用到的密钥全是为这篇文章临时生成的:一把 RSA 2048 的 “upload” 密钥,分别存成 JKS 和 PKCS#12;再按 Android 文档的方式生成一个调试 keystore(别名 androiddebugkey,密码 android)。应用是一个空工程(com.reareverse.certdemo,minSdk 24),用这些密钥签名。我没有读这台机器上真实的 ~/.android/debug.keystore:跑 Gradle 时把 ANDROID_USER_HOME 指到了临时目录,调试签名配置也写死成自己生成的那个文件。

还没开始查指纹,第一个坑就来了:JDK 17 的 keytool 没有 -version 这个选项。执行后先输出 非法选项: -version(系统语言是中文,所以是中文),再跟一大段帮助。想知道用的是哪个 JDK 的 keytool,用 java -version。语言问题后面还会遇到:keytool 和 Gradle 的输出都会本地化,想用 grep 抓 SHA1:,给 keytool 加上 -J-Duser.language=en。

方法一:用 keytool 查 keystore

keytool -J-Duser.language=en -list -v -keystore upload.jks -alias upload -storepass sample123

输出(有删减):

Alias name: upload
Entry type: PrivateKeyEntry
Owner: CN=REA Sample Upload, O=rea-reverse.com, C=US
Serial number: dc473b319601792c
Certificate fingerprints:
	 SHA1: 0F:9C:63:67:43:7A:67:69:81:F2:D5:6F:5C:A8:64:F4:90:47:AA:36
	 SHA256: 11:82:12:DD:1B:8D:76:BD:E7:4F:F4:81:80:47:ED:4F:43:2E:BF:21:D8:A0:A8:9D:18:84:2B:69:C1:FB:40:10
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key

这里有两点要注意。

-v 不能省。 不加 -v 时,JDK 17 的 keytool -list 每个条目只打一行 Certificate fingerprint (SHA-256): 11:82:12:…,简短列表里根本没有 SHA-1,而 Google 登录的 Android OAuth 客户端要的恰恰是 SHA-1。

要不要密码,看 keystore 的格式。 JKS 里的证书是明文存的:同样的命令去掉 -storepass,在密码提示处直接回车,keytool 会先打一个 “WARNING WARNING WARNING … integrity … has NOT been verified” 的警告框,然后照样给出 SHA-1 和 SHA-256。PKCS#12 则会加密证书。upload.p12 不给密码时我得到的是:

Keystore type: PKCS12
Alias name: upload
Entry type: PrivateKeyEntry
Certificate chain length: 0

没有指纹,也不报错。密码输错反倒会明确失败:keytool error: java.io.IOException: keystore password was incorrect。另外,文件扩展名说明不了格式:不带 -storetype 执行 keytool -genkeypair -keystore defaulttype.jks,生成的文件 Keystore type: PKCS12,因为从 JDK 9 起 keytool 默认就是 PKCS#12。本站的指纹查看器表现一样,因为数据本来就是加密的:JKS 不输密码也能列出证书(完整性显示为“未校验”),PKCS#12 只能列出别名,证书显示为已锁定,输入密码后才能读出。

方法二:./gradlew signingReport

如果 keystore 已经配进工程,Gradle 会打印每个变体的签名证书。我第一次跑在任务执行前就失败了:

Failed to apply plugin 'com.android.internal.version-check'.
   > Minimum supported Gradle version is 9.6.0. Current version is 9.4.1.

原因是我直接用了 Homebrew 装的 gradle(9.4.1),而 AGP 9.4.1 要求更高版本。正式工程里应该用 wrapper,也就是 ./gradlew,它会锁定匹配的 Gradle 版本。换成 Gradle 9.6.1 后任务正常完成(BUILD SUCCESSFUL in 447ms):

> Task :app:signingReport
Variant: debug
Config: debug
Store: …/app/debug.keystore
Alias: androiddebugkey
MD5: 4D:4D:46:26:76:83:1F:42:54:3F:50:73:C2:AD:FD:97
SHA1: 06:AC:D5:48:20:5B:9B:28:55:82:C8:D8:47:D5:D1:41:A3:68:5B:BE
SHA-256: 84:EA:8F:71:7D:45:10:4E:05:F7:68:5D:AA:B4:8C:B4:DF:A6:45:51:3A:D8:5E:E3:85:77:5B:05:D1:BC:D7:A7
Valid until: 2054年3月6日星期五
----------
Variant: release
Config: release
Store: …/app/upload.jks
Alias: upload
SHA1: 0F:9C:63:67:43:7A:67:69:81:F2:D5:6F:5C:A8:64:F4:90:47:AA:36
SHA-256: 11:82:12:DD:1B:8D:76:BD:E7:4F:F4:81:80:47:ED:4F:43:2E:BF:21:D8:A0:A8:9D:18:84:2B:69:C1:FB:40:10

release 的摘要和 keytool 完全一样。日期又被本地化了,不影响使用,但也说明这份报告是给人看的,不适合拿脚本解析。signingReport 还有一个局限:它读的是构建脚本里的签名配置,没法告诉你 Google Play 实际用哪把钥匙签名(见下文“该用哪把钥匙”),别人打的包它也管不了。

离线构建 release APK 时还碰到一个和签名无关的问题:lintVitalAnalyzeRelease 需要 com.android.tools.lint:lint-gradle:32.4.1,而我的 Gradle 缓存里没有(“No cached version … available for offline mode”)。为了这次测试,我加了 lint { checkReleaseBuilds = false } 才拿到签名好的 APK;正常联网构建时,lint 还是开着好。

方法三:用 apksigner 查打好的 APK

keystore 只说明你打算用哪把钥匙签,APK 才说明你实际发出去的是什么。apksigner 在 Android SDK 的 build-tools 里:

~/Library/Android/sdk/build-tools/36.1.0/apksigner verify --print-certs -v 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): false
Number of signers: 1
Signer #1 certificate DN: CN=REA Sample Upload, O=rea-reverse.com, C=US
Signer #1 certificate SHA-256 digest: 118212dd1b8d76bde74ff4818047ed4f432ebf21d8a0a89d18842b69c1fb4010
Signer #1 certificate SHA-1 digest: 0f9c6367437a676981f2d56f5ca864f49047aa36
Signer #1 certificate MD5 digest: cefddf40e5ac53c760ad45660de935f3

SHA-1、SHA-256 都对得上,只是小写、没有冒号。Firebase 和 assetlinks.json 要的是 0F:9C:… 这种格式,得自己转一下;指纹查看器 两种格式都有,各带一个复制按钮。

再看 v1 scheme: false:minSdk 24 时,AGP 9.4.1 只用 v2 给 APK 签名。所以老办法在它身上会失灵:

keytool -printcert -jarfile app-release.apk
Not a signed jar file

keytool -printcert -jarfile 只认 JAR(v1)签名。APK 明明签过名,只是 keytool 看不到 v2 签名块。AAB 正好反过来:bundleRelease 产出的 app-release.aab 有 643,520 字节,Gradle 用 JAR 签名给它签,所以 keytool 能正常读,输出同样的 SHA1: 0F:9C:63:…;而 apksigner 对同一个 AAB 直接抛异常:

Exception in thread "main" com.android.apksig.apk.ApkFormatException: Missing AndroidManifest.xml

结论:APK 用 apksigner,AAB 用 keytool -printcert -jarfile,或者两种都丢给指纹查看器。它对这两个文件分别报告 APK 是 v2、AAB 是 v1,摘要相同。

该用哪把钥匙的指纹

一个应用最多会牵涉三把钥匙。Google 登录报 developer error,配置却看上去全对,最常见的原因就是填错了钥匙。

你测试的版本 签名用的钥匙 指纹从哪儿取
Android Studio 跑出来的 debug 包 这台电脑的调试 keystore signingReport 的 debug 变体
自己侧载或上传的 release APK 你的上传密钥 / 发布密钥 对 keystore 跑 keytool -list -v,或对 APK 跑 apksigner
启用了 Play 应用签名、从 Google Play 安装的版本 Google 托管的应用签名密钥 Play 管理中心的应用签名页面;你手上的任何文件里都没有

每台开发机都有自己的调试 keystore,所以同事的 debug SHA-1 和你的不一样。对于从 Play 安装的版本,上传密钥的指纹是错的,用户装到手上的包是由 Play 管理中心页面上那把应用签名密钥签的。这一步我没法在这里演示,因为需要一个 Play 管理中心里的应用;要点是,没有任何本地命令能把它打印出来。

Firebase 和 Google 登录

Firebase 项目设置里可以填冒号十六进制格式的 SHA-1 和 SHA-256,而且可以填多个(debug、上传密钥、Play 应用签名密钥)。Google 登录用的 Android OAuth 客户端以包名加 SHA-1 为准。这篇文章没有在真实的 Firebase 项目里登记,控制台的具体字样请以 Firebase 文档 为准。

Android App Links:assetlinks.json

App Links 验证要的是 SHA-256,大写冒号十六进制,放在 https://你的域名/.well-known/assetlinks.json:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.reareverse.certdemo",
    "sha256_cert_fingerprints": [
      "11:82:12:DD:1B:8D:76:BD:E7:4F:F4:81:80:47:ED:4F:43:2E:BF:21:D8:A0:A8:9D:18:84:2B:69:C1:FB:40:10"
    ]
  }
}]

启用了 Play 应用签名的话,这里填应用签名密钥的 SHA-256;如果你还会安装用上传密钥签名的 release 包,把上传密钥的也加一项。参见 Verify Android App Links。

Facebook key hash:那条会骗人的命令

Facebook 登录要填 “key hash”,也就是 SHA-1 的 Base64。常见写法是:

keytool -exportcert -alias upload -keystore upload.jks -storepass sample123 | openssl sha1 -binary | openssl base64
D5xjZ0N6Z2mB8tVvXKhk9JBHqjY=

和指纹查看器里 Base64 那一栏一致,也就是 0F:9C:63:… 这 20 个字节。接着我手滑,用了一个这个 keystore 里不存在的别名(androiddebugkey),同一条命令输出:

xSLzBBuLJunOQPB89rtzM54FXx4=

看着像一个正常的 28 位 key hash,但它是错的。keytool 把 keytool error: java.lang.Exception: Alias <androiddebugkey> does not exist 写到了标准输出,openssl sha1 算的就是这句报错,整条管道也不报任何错误。所以先单独跑一遍 keytool -exportcert 那一半,或者拿结果和 SHA-1 对照:key hash 做 Base64 解码后,应该正好是 SHA-1 那 20 个字节。

浏览器里查,以及实测数据

签名证书指纹查看器 能读 JKS、JCEKS、PKCS#12 keystore,APK 和 AAB 的签名块(v1、v2、v3、v3.1),以及证书文件。我在 Node 22 下用页面同一套解析代码跑了本文的文件:

文件 大小 结果 解析耗时
upload.jks,不输密码 2,169 B 1 个私钥条目,SHA-1 0F:9C:63:…,完整性未校验 2.8 ms
upload.p12,不输密码 2,664 B 列出别名,证书已锁定 28 ms
upload.p12,输入密码 2,664 B SHA-1 E3:59:F5:…,PBES2 / AES-256-CBC,HMAC-SHA256 MAC 校验通过 26 ms
app-release.apk 642,546 B v2 签名者,摘要与 apksigner 一致 2.7 ms
app-release.aab 643,520 B v1 签名者,摘要与 keytool 一致 11.9 ms

PKCS#12 那几十毫秒花在 10,000 次迭代的 PBKDF2 和 MAC 上,不是解析本身。在浏览器里,这些都跑在一个不能联网的 Web Worker 里;密码只用来校验 MAC、解密证书包,私钥从不解密。

测试时我还把这个 PKCS#12 重新编码成 BER(不定长编码,authenticated safe 拆成分段的 OCTET STRING,一些 Windows 和 NSS 导出的文件就长这样)。openssl pkcs12 和 keytool 都能照常读,指纹查看器也能读,MAC 校验通过。工具页原来把 BER 编码的 PKCS#12 列为不支持,这条限制已经不存在了,页面说明也一并改了。

同一轮测试里还有个 macOS 特有的坑:用 security import 把 JDK 17 默认格式的 PKCS#12 导入临时钥匙串,报 MAC verification failed during PKCS12 import (wrong password?),可密码是对的。换成旧格式(keytool -J-Dkeystore.pkcs12.legacy -importkeystore …)就能导入。如果“钥匙串访问”拒收一个其他工具都能打开的 keystore,先试试这个办法,别急着怀疑密码。

REA 在这里能做什么

REA(morluto 的开源项目)负责的是 APK 的代码层面:6.7.0 版的 Android 工具通过无界面的 JADX 给出清单声明、类和反编译后的方法(Android 指南)。我检查了 npm 上发布的 [email protected] 包,里面没有读取签名证书的功能,也没有引用 apksigner 或 keytool。所以指纹用上面这些工具查,等问题变成“这个包到底做了什么”时再用 REA。只分析你自己构建或已获授权测试的应用,详见 REA 的 DISCLAIMER.md。

下一步