Skip to content

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:

  1. Delivery integrity. Google's own delivery metadata (sizes and digests) is checked against the files on disk.
  2. 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:

ObjectFieldWire typeMeaningEncoding
base delivery10APK sizeprotobuf varint
base delivery22SHA-1ASCII unpadded Base64url
base delivery192SHA-256ASCII unpadded Base64url
split message12split nameUTF-8
split message20APK sizeprotobuf varint
split message42SHA-1ASCII unpadded Base64url
split message92SHA-256ASCII 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:

ToolVersionRole
apkutils21.0.0Binary Android manifest decoding
Google apksignerbundled with pinned uber-apk-signer v1.3.0APK 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:

text
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:E1

All 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:

bash
MORPHE_LIVE_TEST_PACKAGE=com.example.app uv run python -m unittest tests.test_morphe_builder.LiveGooglePlayTest

Released under the MIT License.