Android decides whether to install a package by checking a cryptographic signature, not by looking at the file name. That check answers two questions: was this file signed by the holder of a specific private key, and is it the same key that signed the app already on the device? Everything else — the icon, the version string, the download page — is metadata that anyone can copy.
The current Hesgoal build is signed with a v2 signature only, under the certificate identity CN=VVStreams. This article explains what that certificate is, how v2 differs from the older v1 scheme, why a different signer blocks an update, and how to read the certificate with apksigner.
What a signing certificate actually is
An APK signature starts with a key pair. The developer holds the private key; the APK contains a certificate with the matching public key and a subject name. Signing computes digests over the package and encrypts them with the private key. Android recomputes the digests and verifies them with the public key.
That certificate is self-signed. It is not issued by a certificate authority, it does not prove a company name, and nothing validates the CN=VVStreams string against a business register. Android’s model is continuity, not identity assurance: the same key must sign every update, and the package name plus the key pair becomes the app’s identity on the device.
What Android checks at install time
Three things happen before the package manager commits an install:
- The signature is cryptographically valid over the bytes the scheme covers.
- The device’s Android version supports the signature scheme used. v2 requires Android 7.0 (API 24) or newer, which is exactly the minimum the app declares.
- For an update, the new package’s signer matches the existing installation, or a valid rotation lineage proves continuity.
The Hesgoal certificate
| Property | Value |
|---|---|
| Package | com.vvstreams.app |
| Version | 3.3 |
| Signer DN | CN=VVStreams |
| Key algorithm | RSA 4096 |
| Signature scheme | v2 only |
| Certificate SHA-256 | 0fe99515295e79461b8f49d1397a703c327777a0b8cf83cbaecb6cef64de686d |
The certificate digest is a SHA-256 over the DER-encoded certificate, not over the APK. It is a stable identifier for the signing identity across every version signed with that key. The APK hash changes with every build; the certificate digest should not.
How v2 differs from v1
Version 1 is JAR signing, inherited from Java. It stores per-entry digests in META-INF/MANIFEST.MF, a signature file, and a PKCS#7 block. The important limitation is what it does not cover: ZIP metadata outside the entry contents. That gap produced the Janus vulnerability (CVE-2017-13156), where an attacker could prepend a DEX file to a signed APK and have affected Android versions execute code the signature never covered.
Version 2 was introduced in Android 7.0 to close that class of attack. It places a signing block immediately before the ZIP central directory and computes digests over the whole file in 1 MB chunks, including the central directory, the ZIP metadata and the entry data. Verification happens before the package is parsed, so a modified byte anywhere in the covered region invalidates the signature.
| Scheme | What it covers | Minimum Android | Key rotation |
|---|---|---|---|
| v1 | Individual ZIP entries | All versions | No |
| v2 | Entire APK, chunked digest | 7.0 (API 24) | No |
| v3 | Same coverage as v2 plus lineage | 9 (API 28) | Yes |
| v4 | Companion file for incremental install | 11 (API 30) | Via v3 lineage |
v3 adds a proof-of-rotation structure so a key can be replaced without breaking updates. This build does not use it. A v2-only signature is valid and complete for installation on Android 7.0 and newer, but it means the signing key cannot be rotated later without changing the package identity.
Why a mismatched signer blocks updates
Android assigns each package a Linux user ID derived from the signing identity. App data lives under that UID. If a package with a different signer could update an existing app, it would inherit access to that app’s private storage — databases, cached tokens, preferences. The signature check on update exists to prevent exactly that.
When you try to install an APK over an existing app signed by a different key, the package manager returns INSTALL_FAILED_UPDATE_INCOMPATIBLE. The error is not a bug and has no override. The only paths are to uninstall the existing app first, which deletes its data, or to obtain a build signed with the original key.
This is also why a repacked APK cannot quietly replace the official one. If a third party rebuilds the package, even with identical code, the signature changes and the update fails. The inconvenience is deliberate.
What to do when the signer differs
- If you deliberately switched to a modified build, uninstall before installing the official file.
- If you did not, treat the mismatch as a warning. A file claiming to be the same app but signed by another key is not the same app.
- Check the certificate digest before installing anything new. The value is published on the download page, and the security page explains the signing model.
Reading the certificate with apksigner
apksigner ships with the Android SDK build tools. It reads v1, v2, v3 and v4 signatures and prints the certificate chain.
apksigner verify --print-certs hesgoal.apk
The output has this shape:
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=VVStreams
Signer #1 certificate SHA-256 digest: 0fe99515295e79461b8f49d1397a703c327777a0b8cf83cbaecb6cef64de686d
Signer #1 signature algorithm: SHA256withRSA
Signer #1 public key algorithm: RSA
Signer #1 key size (bits): 4096
Two lines matter. Verified using v2 scheme: true confirms the whole-file signature is intact. The certificate SHA-256 digest is the identity value to compare against the published one, and it should match the table above exactly.
Useful flags:
--verboseprints the full scheme list and the digest algorithms used.--min-sdk-version 24forces verification at the app’s real minimum. Without it,apksignerassumes a lower API level and may report v1 failures that do not apply to devices this package can install on.--print-certsis what surfaces the DN and digest. Without it you only get a pass or fail.
The digest is not the APK hash
A common mistake is comparing the certificate digest with the SHA-256 of the file. They are different values computed over different inputs. The file hash identifies this build; the certificate digest identifies the key. You need both. The hash verification guide covers the file side, and the publisher page explains why the certificate subject is a developer entity rather than the product name.
keytool does not read this file
keytool -printcert -jarfile reads JAR signatures only. On a v2-only APK it reports that the JAR is not signed, which looks alarming and is the wrong tool. Use apksigner for v2 and later.
What the signature does not prove
A valid signature proves the bytes were signed by the key in the certificate, and that the file has not changed since. It does not prove:
- that the code is free of vulnerabilities or unwanted behaviour;
- that the third-party sources the app connects to are lawful or well-run;
- that the publisher holds rights to the content the app links to;
- that every permission is necessary for the feature you use.
The last point is worth repeating with specifics. This build declares SYSTEM_ALERT_WINDOW and REQUEST_INSTALL_PACKAGES, neither of which playback requires. It also sets usesCleartextTraffic=true with no network security config, so unencrypted HTTP requests are permitted. A signature cannot make those choices safer; only the permission audit and your own decisions can. The safety audit covers both.
There is also a structural limit to self-signed certificates: they establish continuity, not trustworthiness. If the private key were stolen, an attacker could sign a malicious update that Android would accept as genuine. That risk exists for every Android app, and it is why key custody matters more than the distinguished name.
A verification routine
For any APK you intend to keep:
- Compare the SHA-256 with the published value. A mismatch means stop; there is no partial match to interpret.
- Run
apksigner verify --print-certsand compare the certificate digest. - Confirm the signature scheme matches the published build. A v2-only package that reports v3 is not the same artifact.
- Read the permission list with
aapt2 dump permissionsand decide on the two appop-gated entries.
The current build reports a single signer with an RSA 4096 key and a v2 signature. That is internally consistent: one signer, no rotation lineage, one universal APK. If a file you have reports multiple signers or a different digest, it is a different artifact regardless of what the filename says.
The short version
A signing certificate binds a public key to a package name. v2 covers the entire APK, including ZIP metadata that v1 left unprotected. A different signer cannot update an installed app because Android derives app data ownership from the signing identity. The Hesgoal build is signed by CN=VVStreams with an RSA 4096 key under the v2 scheme, and its certificate digest is published so you can confirm the file before installing. Start at the download page and check both values.