Google Play delivery integrity
A stock APK downloaded from the internet is untrusted input. This document describes how the builder establishes that a downloaded split set really is the requested Google-signed app, before anything is merged, patched, or published.
Downloads go through reviewed goopdl releases from PyPI; pyproject.toml and uv.lock pin the exact version, which provenance records. Downloader fixes stay upstream of the builder's subprocess boundary, so the builder never reaches into goopdl internals.
Two independent layers
Verification is deliberately layered, and the second layer does not trust the first:
- Delivery integrity. Google's own delivery metadata (sizes and digests) is checked against the files on disk.
- APK identity. Package, version, architecture, split requirements, and signer are re-derived from the APK bytes themselves.
A compromised or buggy downloader could produce a self-consistent manifest; it cannot make an APK claim a package and signer it does not have.
Layer 1: delivery fields
The reviewed fork's parser and a sanitized live Google Play fixture establish these delivery fields:
| Object | Field | Wire type | Meaning | Encoding |
|---|---|---|---|---|
| base delivery | 1 | 0 | APK size | protobuf varint |
| base delivery | 2 | 2 | SHA-1 | ASCII unpadded Base64url |
| base delivery | 19 | 2 | SHA-256 | ASCII unpadded Base64url |
| split message | 1 | 2 | split name | UTF-8 |
| split message | 2 | 0 | APK size | protobuf varint |
| split message | 4 | 2 | SHA-1 | ASCII unpadded Base64url |
| split message | 9 | 2 | SHA-256 | ASCII unpadded Base64url |
SHA-256 is preferred; SHA-1 is accepted only when SHA-256 is absent for a file.
The manifest deliberately excludes download URLs, cookies, auth tokens, and dispenser data, so it can be written to disk and kept in provenance without leaking credentials. Before any APK is opened, the builder independently validates the manifest schema, path safety, complete file coverage (no extra and no missing files), size, and digest.
tests/fixtures/google-delivery-sanitized.json records a real YouTube Music base plus density and language splits. Google supplied both SHA-1 and SHA-256 for every file. The fixture excludes APK bytes, URLs, cookies, tokens, headers, auth cache, and dispenser responses; dispenser failures are redacted by the reviewed fork.
Layer 2: APK inspection
Two pinned tools do the inspection:
| Tool | Version | Role |
|---|---|---|
apkutils2 | 1.0.0 | Binary Android manifest decoding |
Google apksigner | bundled with pinned uber-apk-signer v1.3.0 | APK signature verification and complete v3.1 signing-lineage certificate extraction |
Metadata re-derived from each APK includes package, version name and code, split name and type, ABI, density, language, feature/config target, required split declarations, required hardware and software features, and signer certificate SHA-256.
Every split in a set must carry an identical signer identity. A mixed-signer set is rejected, since that is what a substituted split looks like.
Signer pins
Current Google Play signer pins, confirmed from fresh verified ARM64 split sets and independently corroborated by APKMirror:
YouTube, Android 7-12: 3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A
YouTube, Android 13+: 5A:AD:2B:EE:6D:B9:5D:17:E0:5A:08:D7:D1:E6:4C:10:A1:51:18:79:15:44:83:91:6B:6A:E6:C7:FD:9C:B0:C6
YouTube Music, Android 7-12: A2:A1:AD:7B:A7:F4:1D:FC:A4:51:4E:2A:FE:B9:06:91:71:9A:F6:D0:FD:BE:D4:B0:9B:BF:0E:D8:97:70:1C:EB
YouTube Music, Android 13+: 6A:2F:65:EC:69:4A:6A:63:2A:CD:CB:50:80:91:2A:56:5F:90:3D:4B:8D:83:F0:EB:8E:44:FB:DF:26:60:D8:E1All splits in each downloaded set must expose the same complete signing lineage. A pinned package passes only when that verified lineage exactly matches the reviewed legacy and rotated certificates; missing or additional certificates fail until reviewed.
Other packages accept one cryptographically valid, internally consistent split signer by default. Its SHA-256 is recorded in provenance and build summaries, and the build logs unpinned signer accepted. That default trusts whichever signer the selected provider served, which is a real, documented limitation rather than a proof of authenticity.
Set a per-app expected-signer to opt into a SHA-256 pin for any other app; the value can be taken from a first verified download's provenance. See configuration.md.
Publication
Downloads land in a unique temporary directory that is a sibling of the final output, so publication is a rename on the same filesystem.
Any subprocess failure, delivery-integrity failure, manifest failure, version mismatch, split problem, ABI mismatch, or signer mismatch removes the temporary set. Nothing partial is ever left behind.
A successful set receives a deterministic provenance.json and the directory is then atomically renamed into place. Existing trusted output is never overwritten; the build refuses with exit code 9 instead.
Live validation evidence
Validated with the builder's opt-in live test against the downloader implementation later hard-forked and published as goopdl:
- YouTube ARM64
21.30.206-SECONDARY(1561187801): base, ABI, language, and density splits verified and published atomically. - YouTube Music ARM64
9.28.51(92851240): base, language, and density splits verified and published atomically. - Every file carried Google SHA-1 and SHA-256 metadata; SHA-256 was selected and independently recalculated.
- Package, version code, base version name, split requirements, ABI, signatures, signer pin, provenance, and published file coverage all passed.
- Test outputs used temporary directories and retained no APKs, authenticated URLs, cookies, or tokens.
Run it yourself with an existing auth outside the repository:
MORPHE_LIVE_TEST_PACKAGE=com.example.app uv run python -m unittest tests.test_morphe_builder.LiveGooglePlayTest