Published · 9 min read
A file hash is not a signing certificate
The APK Analyzer prints SHA-256 of every byte you drop. apksigner verify --print-certs prints SHA-256 of the signer certificate. Play Console shows an upload certificate and an app signing certificate. Those values answer three different questions. Lining the wrong pair up looks like a failed release when the bytes are fine, or like a match when nobody has checked the signature.
Three SHA-256 strings
| Value | What it covers | Where you get it |
|---|---|---|
| Whole-file digest | Every byte of that APK or AAB, including ZIP headers and any signature block already inside it. | APK Analyzer, sha256sum, or Get-FileHash. They should agree on the same file. |
| Signer certificate digest | The certificate that signed this archive, not the archive itself. | apksigner verify --print-certs your.apk. The signer page does not print this flag for you. |
| Play Console fingerprints | The upload certificate you enrolled, and the app signing certificate Google uses when Play App Signing is on. | Play Console, App signing. This site never loads that page and never sees those fingerprints. |
A whole-file digest changes when any byte changes: a rebuilt ZIP, a different alignment, a second signature, or Play replacing the upload signature with the app signing signature. A certificate digest stays the same across every APK that key signs, and it changes when you switch keystores. Play’s app signing fingerprint is a third number when Google holds the app signing key. Comparing a file hash to a certificate fingerprint will not succeed for a reason that has nothing to do with corruption. The strings are different lengths of meaning even when both are 64 hex characters.
Sample numbers, kept apart
Two sample values are already published on this site. They are not a verification of each other, and neither is a Play upload certificate.
- Whole-file SHA-256 of the sample
notes-base.apk:df08881c47b751768eaf5bb11b836fcef8ccd13aaf665aff8b8a242a6e7c2d93. The analyzer report for that file is on the APK Analyzer page. The heuristic block on that sample does not print a package name.aapt dump badgingdoes. The hash does not depend on the heuristic. - Certificate SHA-256 for the sample key subject
CN=Example:6013eea4f0172bbc2fac2f69d7bf96a2fe59635780ed8c4dfc7ae1a84fd92e67. That line is fromapksigner verify --print-certson a sample signed with build-tools 36.0.0. It was not enrolled in Play App Signing. Do not paste it into Play Console as your upload certificate.
If those two hex strings differ, nothing is wrong with the samples. One hashes a ZIP. The other hashes a certificate. Putting them in the same spreadsheet column is the mistake this page is about.
The analyzer also lists ZIP entry names. META-INF entries, when they appear, mean those names are in the archive. Their presence does not mean apksigner verify would pass, and it does not say whether the signature is v1, v2, or v3. The page never asks for a keystore and never sends the file to droidxp.com. JSZip loads from jsDelivr so the listing can run. The digest itself is crypto.subtle.digest('SHA-256', …) of the whole dropped buffer.
What a file-hash match can prove
Use the analyzer’s SHA-256, or the same hash from sha256sum or Get-FileHash, when the question is “is this the file CI built?” A match against the CI log means those bytes are the artifact you tagged. A mismatch means you are holding a different file: another variant, a rebuilt ZIP, or a download that was changed.
That match does not prove the signature is valid. The analyzer will hash an unsigned APK, a truncated download, and an APK whose v2 block was broken by a later zipalign. You still run verify. The match also does not prove Play will accept the upload. Play checks the upload certificate on the bundle or APK you send. The file hash is not an input to that check.
A match between the desk file and an APK pulled back off a phone proves the phone has that file only if nothing re-signed it on the way. adb install of the exact APK you hashed should round-trip. An install from a Play track, with Play App Signing on, is a different byte stream. The longer account of that mismatch is the desk hash versus the Play-delivered APK.
The commands the signer page prints
The APK signer guide collects paths and options and prints shell text. It does not open a keystore, does not ask for a password, and does not add --ks-pass or --key-pass. apksigner and keytool prompt in your terminal. The distinguished-name field is a certificate subject such as CN=App. A password typed there is copied into -dname and is not a secret the tool stored on purpose. Do not put a password in that field.
Default strings, which you can edit, are release.keystore, alias release, app-release-unsigned.apk, app-release-aligned.apk, and app-release-signed.apk. The page does not check that those files exist. Key algorithm and key size are normalized before they are printed: an EC key will not keep an RSA size such as 2048, and an RSA key will not keep an EC size such as 256.
With the method set to apksigner, the blocks are:
keytool -genkeypair -v -keystore … -alias … -keyalg … -keysize … -validity … -dname …once, to create a keystore. The comment in the block says the password prompt is yours.zipalign -v -p 4 UNSIGNED ALIGNED. Page alignment comes before apksigner. Ifzipalignis not onPATH, the comment points atbuild-tools/<version>/zipalign. The page does not search your SDK directory.apksigner sign --ks KEYSTORE --ks-key-alias ALIAS --out SIGNED ALIGNED. With the v1+v2 box checked, the command also passes--v1-signing-enabled true --v2-signing-enabled true. With the box unchecked, those two flags are omitted and the comment says defaults depend on the build-tools version. The checkbox does not add a v3 flag.apksigner verify --verbose SIGNED. This block checks that a signature scheme verifies. It does not print the certificate SHA-256. Add--print-certsyourself when you need the fingerprint:apksigner verify --print-certs SIGNED.
With the method set to jarsigner, the order is the legacy v1 order. jarsigner signs the unsigned APK in place (jarsigner -verbose -sigalg … -digestalg SHA-256 -keystore … UNSIGNED ALIAS), then zipalign -v -p 4 writes the aligned file, then jarsigner -verify -verbose -certs runs on the aligned file. Keep a copy of the unsigned APK. jarsigner rewrites its input. This path does not produce a v2 block. Prefer the apksigner method for anything you will upload to Play.
This is apksigner verify --verbose on a sample APK signed with build-tools 36.0.0 after zipalign -p 4. Subject of that sample key is CN=Example. Passwords are not in the output. v3 is true in this sample because that build-tools version signed v3, not because the form’s checkbox turns v3 on.
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 Verified using v3.1 scheme (APK Signature Scheme v3.1): false Verified using v4 scheme (APK Signature Scheme v4): false Verified for SourceStamp: false Number of signers: 1
The certificate digest for that same sample key is the 6013eea4… line above, from --print-certs, not from the --verbose block the page prints.
Which order breaks the signature
apksigner wants a page-aligned file and then writes a v2 (and, on current build-tools, often v3) signature over those aligned bytes. If you run zipalign again after that signature, you move ZIP offsets and the v2 block no longer matches. apksigner verify fails. The analyzer still prints a SHA-256, because the file is still a file. A hash of a broken signature is a hash of a broken signature.
jarsigner is the opposite order, and only for v1. You sign first, then align. Aligning after a v1 signature is the documented jarsigner flow. Aligning after apksigner is not. The signer page prints one order or the other depending on the method you picked. It will not stop you from pasting the blocks into a script in a different sequence. Read the headings on the blocks before you save a shell script.
Gradle projects that already have a signingConfig do not need these commands to produce a release. You can still run apksigner verify --verbose and apksigner verify --print-certs on Gradle’s output. Do not point keytool at Play’s app signing key. You do not have that key if Google holds it.
Upload key and app signing key
With Play App Signing you hold an upload key. You sign the Android App Bundle, or the APK, with that key and you upload it. Google checks the upload signature and signs the APKs it delivers with the app signing key. Devices verify the app signing key. The certificate on the uploaded artifact and the certificate on the installed APK are different certificates unless you are in the older case where the same key is still the app signing key.
That is why the analyzer’s whole-file SHA-256 of your upload and the SHA-256 of an APK a tester installed from Play differ even when versionName matches. The signature block differs, so the bytes differ. Compare versionCode and application id first. Compare certificate fingerprints when the question is which key. Compare the analyzer hash when the question is which file.
The upload will be rejected when apksigner verify --print-certs on the bundle you just built does not match the upload certificate shown in Play Console. The analyzer’s file hash will not explain that rejection. There is no fingerprint on this website that you should copy into Play Console. The CN=Example digest is a sample.
Confident wrong notes
- Treating the analyzer line as the upload certificate, or pasting the sample
6013eea4…digest into Play because it was printed next to an APK hash. - Calling a release good because the file hash matches CI, without
apksigner verify. Unsigned and badly aligned files hash just as well. - Running
zipalignon the output of apksigner because the jarsigner habit is “sign, then align.” v2 breaks. The file hash changes too, so the CI comparison fails for a second reason. - Hashing the
.jksor.keystorefile and calling that digest the certificate. The keystore is a container. The certificate digest comes from--print-certsor from Play Console. - Expecting the analyzer hash of an
.aabto match any APK. The analyzer does not unpack a bundle. Hash the bundle to record the upload, and hash APKs separately. bundletool is how you produce APKs from the bundle. This site does not run it. - Comparing a Play-installed APK to the upload artifact and filing a corrupt-build bug. Read the Play-delivered hash post before you do that.
What to keep with the release
On the release ticket, store four facts from the file you uploaded:
- The whole-file SHA-256 from the analyzer or from
sha256sum/Get-FileHash. - versionCode and applicationId, from
aapt dump badgingor from Play Console after upload. The analyzer’s “possible version” line is a string scan and may be blank, as it is onnotes-base.apk. - The upload-certificate SHA-256 from
apksigner verify --print-certson that same artifact. - The app signing certificate fingerprint from Play Console, copied once when enrollment shows it. Installed APKs should match that certificate. They should not be expected to match the upload file’s hash.
Keep the keystore out of git. The keytool block is a reminder of the flags, not a place to paste a password into a ticket. If verify fails, fix alignment and the keystore path before you look at the file hash. The hash will still be there after the signature is fixed, and it will be a new hash, because signing changes bytes.
Related pages: the APK Analyzer, the signer guide, desk hash versus the Play-delivered APK, the publishing checklist, and the merged manifest is not the XML you pasted.