adityatelange/sign-apk-py

Sign Android APKs with APK Signature Schemes v2, v3, and v4, in pure Python.

0

stars

4

commits

Python

primary language

Aug 22, 2026

updated

android
apk
apksigner
madewithai
sign-apk

README

sign-apk-py

License: Unlicense Python 3.9+

Sign Android APKs with APK Signature Schemes v2, v3, and v4, in pure Python.

No apksigner, jarsigner, zipalign, keytool, openssl, or Android SDK required. The only dependency is cryptography for RSA/EC primitives and X.509 handling. The ZIP surgery, APK Signing Block construction, and the chunked content-digest algorithm are all implemented here directly, and the output is verified against Android's own apksigner.

Features

  • v2, v3, and v4 signing in one tool, no JDK or Android SDK install
  • Zero-config debug signing: generates a throwaway self-signed key if you don't have one
  • PKCS#12 and PEM key support for release signing
  • Byte-for-byte preservation of entry contents and compression, so signing never touches your app's actual payload
  • zipalign-compatible output: 4-byte and 16 KB alignment are preserved, so a separate zipalign pass isn't needed
  • Built-in verifier for a fast sanity check without shelling out

Install

Requires Python 3.9+. Not published on PyPI yet, so install straight from the git repo as a standalone CLI:

# uv
uv tool install git+https://github.com/adityatelange/sign-apk-py

# pipx
pipx install git+https://github.com/adityatelange/sign-apk-py

Either way, this puts sign-apk on your PATH, isolated from your other Python environments.

To hack on it instead, clone and install editable:

git clone https://github.com/adityatelange/sign-apk-py.git
cd sign-apk-py
uv pip install -e ".[dev]"   # or: pip install -e ".[dev]"

Usage

Three subcommands: sign, generate-key, and verify.

Quickstart

No key yet? Just sign. A throwaway debug key is generated on the fly:

sign-apk sign app-unsigned.apk app-signed.apk

For a real release, pass a key and certificate:

sign-apk sign app-unsigned.apk app-signed.apk --key release.pem --cert release.crt

Sign an APK

sign-apk sign INPUT OUTPUT [key options] [scheme options]

Key options, in priority order (first match wins):

FlagDescription
--p12 PATHPKCS#12 keystore (.p12/.pfx)
--p12-password PASSPassword for --p12 (prompted if omitted)
--key PATH --cert PATHPEM private key + PEM certificate pair
--key-password PASSPassword for an encrypted --key
(none of the above)Generates a throwaway self-signed debug key
--save-debug-key PREFIXSaves the generated debug key as PREFIX.pem / PREFIX.crt, for reuse across builds
# PKCS#12 / .p12 keystore (prompts for the password if omitted)
sign-apk sign app-unsigned.apk app-signed.apk --p12 release.p12

# Debug key, saved for next time
sign-apk sign app-unsigned.apk app-signed.apk --save-debug-key debug
sign-apk sign app-unsigned.apk app-signed.apk --key debug.pem --cert debug.crt

Scheme options, all optional:

FlagDescription
--v2Emit an APK Signature Scheme v2 block
--v3Emit an APK Signature Scheme v3 block
--v4Write a detached .apk.idsig sidecar (requires --v2 and/or --v3)
--v4-out PATHPath for the v4 sidecar (default: OUTPUT.idsig)
--min-sdk-version NminSdkVersion recorded in the v3 signer (default: 28)

With none of --v2/--v3/--v4 given, both v2 and v3 are written. Pass any of them explicitly to take full control:

# v2 only, for compatibility with Android 7-8 tooling
sign-apk sign in.apk out.apk --key k.pem --cert c.crt --v2

# v2 + v3 + a v4 sidecar for incremental install
sign-apk sign in.apk out.apk --key k.pem --cert c.crt --v2 --v3 --v4
# -> writes out.apk and out.apk.idsig

--v4 anchors to the v2/v3 content digest, so it cannot be used on its own.

Generate a signing key

Generates a self-signed RSA key/certificate pair, independent of signing:

sign-apk generate-key release.pem release.crt --common-name "My Release Key"
FlagDescription
--common-name NAMECertificate CN (default: Android Debug)
--key-size BITSRSA key size (default: 2048)

Verify a signature

sign-apk verify app-signed.apk
VALID (APK Signature Scheme v2+v3)
  v2 signer: CN=My Release Key
  v3 signer: CN=My Release Key (SDK 28-2147483647)

Exits with status 1 and prints INVALID: <reason> on failure, so it's usable directly in CI.

This is a self-check on this tool's own output, not a full reimplementation of Android's verifier (see Scope and limitations), and it doesn't cover v4 sidecars. To verify one of those, use apksigner instead:

apksigner verify --v4-signature-file app-signed.apk.idsig app-signed.apk

Scope and limitations

  • No v1 (JAR) signing, so APKs installing on Android 6 and below will not verify. v2 covers Android 7+, v3 Android 9+.
  • No key rotation. The v3 block is written with a single signer and no proof-of-rotation lineage, which is the common case for apps that are not rotating keys. Rotation (and the v3.1 block that targets it at a minimum SDK) is not implemented.
  • One signer per APK. The format allows several; this writes a single one.
  • Whole file is read into memory. Fine for typical APKs (an 89 MB app signs in about half a second), but not suited to very large files on memory-constrained machines.
  • No ZIP64. APKs above 4 GB, or with more than 65535 entries, are not supported.
  • sign-apk verify is a self-check, not a full reimplementation of Android's verifier. It checks v2 and v3 signatures and content digests, but does not validate certificate chains, expiry, SDK-range coverage across signers, rotation lineages, or v4 sidecars. Use apksigner verify when you need an authoritative answer.

Testing

python -m pytest tests/

The suite covers RSA and EC signing across v2/v3/v4, PKCS#12 and PEM loading, re-signing, content preservation, alignment, Merkle tree construction, and tamper detection. Where apksigner is on PATH, tests additionally assert that it accepts the output and rejects tampered files; those tests skip when it is absent.

One test asserts that this project's Merkle tree reproduces apksigner's byte for byte: given the same input file, the tree bytes and root hash match exactly.

Verified against Android apksigner on a real 89 MB production APK (originally v3-signed): all 1622 entries byte-identical after re-signing, zipalign -c -P 16 clean, v2 scheme: true on SDK 24-27, and v3 scheme: true / v4 scheme: true on SDK 28+.

How it works

Signing rewrites the APK into the layout the v2/v3 schemes require:

[ ZIP entries ]  [ APK Signing Block ]  [ Central Directory ]  [ EOCD ]
                          ^                                      ^
                   signature lives here          central-dir offset patched to match

Along the way it:

  • Strips stale signatures: old META-INF/*.SF / *.RSA / *.DSA / *.EC entries and any previous APK Signing Block (v2 or v3) are removed, so re-signing replaces rather than accumulates. MANIFEST.MF is left alone.
  • Never recompresses: entry payloads are copied verbatim, so contents and compression methods are bit-for-bit preserved.
  • Preserves alignment: uncompressed entries are re-emitted at 4-byte aligned offsets, and uncompressed .so libraries at 16 KB, matching zipalign -P 16. Android memory-maps these directly, so misalignment can break native library loading at runtime.

Key selection follows apksigner's rules: RSA keys under 3072 bits use SHA-256 (0x0103), larger ones SHA-512 (0x0104); EC keys use SHA-256 (0x0201) or SHA-512 (0x0202) by curve size.

References

The binary formats implemented here are specified by Android and PKWARE. Each module docstring cites the specification for the layout, constants, and algorithms it implements, along with the corresponding upstream file and function for cross-checking.

This project is an independent implementation written against those specifications. It is not affiliated with or endorsed by Google or PKWARE.

Contributors

adityatelange

4 commits

adityatelange/sign-apk-py

Sign Android APKs with APK Signature Schemes v2, v3, and v4, in pure Python.

0

stars

4

commits

Python

primary language

Aug 22, 2026

updated

android
apk
apksigner
madewithai
sign-apk

README

sign-apk-py

License: Unlicense Python 3.9+

Sign Android APKs with APK Signature Schemes v2, v3, and v4, in pure Python.

No apksigner, jarsigner, zipalign, keytool, openssl, or Android SDK required. The only dependency is cryptography for RSA/EC primitives and X.509 handling. The ZIP surgery, APK Signing Block construction, and the chunked content-digest algorithm are all implemented here directly, and the output is verified against Android's own apksigner.

Features

  • v2, v3, and v4 signing in one tool, no JDK or Android SDK install
  • Zero-config debug signing: generates a throwaway self-signed key if you don't have one
  • PKCS#12 and PEM key support for release signing
  • Byte-for-byte preservation of entry contents and compression, so signing never touches your app's actual payload
  • zipalign-compatible output: 4-byte and 16 KB alignment are preserved, so a separate zipalign pass isn't needed
  • Built-in verifier for a fast sanity check without shelling out

Install

Requires Python 3.9+. Not published on PyPI yet, so install straight from the git repo as a standalone CLI:

# uv
uv tool install git+https://github.com/adityatelange/sign-apk-py

# pipx
pipx install git+https://github.com/adityatelange/sign-apk-py

Either way, this puts sign-apk on your PATH, isolated from your other Python environments.

To hack on it instead, clone and install editable:

git clone https://github.com/adityatelange/sign-apk-py.git
cd sign-apk-py
uv pip install -e ".[dev]"   # or: pip install -e ".[dev]"

Usage

Three subcommands: sign, generate-key, and verify.

Quickstart

No key yet? Just sign. A throwaway debug key is generated on the fly:

sign-apk sign app-unsigned.apk app-signed.apk

For a real release, pass a key and certificate:

sign-apk sign app-unsigned.apk app-signed.apk --key release.pem --cert release.crt

Sign an APK

sign-apk sign INPUT OUTPUT [key options] [scheme options]

Key options, in priority order (first match wins):

FlagDescription
--p12 PATHPKCS#12 keystore (.p12/.pfx)
--p12-password PASSPassword for --p12 (prompted if omitted)
--key PATH --cert PATHPEM private key + PEM certificate pair
--key-password PASSPassword for an encrypted --key
(none of the above)Generates a throwaway self-signed debug key
--save-debug-key PREFIXSaves the generated debug key as PREFIX.pem / PREFIX.crt, for reuse across builds
# PKCS#12 / .p12 keystore (prompts for the password if omitted)
sign-apk sign app-unsigned.apk app-signed.apk --p12 release.p12

# Debug key, saved for next time
sign-apk sign app-unsigned.apk app-signed.apk --save-debug-key debug
sign-apk sign app-unsigned.apk app-signed.apk --key debug.pem --cert debug.crt

Scheme options, all optional:

FlagDescription
--v2Emit an APK Signature Scheme v2 block
--v3Emit an APK Signature Scheme v3 block
--v4Write a detached .apk.idsig sidecar (requires --v2 and/or --v3)
--v4-out PATHPath for the v4 sidecar (default: OUTPUT.idsig)
--min-sdk-version NminSdkVersion recorded in the v3 signer (default: 28)

With none of --v2/--v3/--v4 given, both v2 and v3 are written. Pass any of them explicitly to take full control:

# v2 only, for compatibility with Android 7-8 tooling
sign-apk sign in.apk out.apk --key k.pem --cert c.crt --v2

# v2 + v3 + a v4 sidecar for incremental install
sign-apk sign in.apk out.apk --key k.pem --cert c.crt --v2 --v3 --v4
# -> writes out.apk and out.apk.idsig

--v4 anchors to the v2/v3 content digest, so it cannot be used on its own.

Generate a signing key

Generates a self-signed RSA key/certificate pair, independent of signing:

sign-apk generate-key release.pem release.crt --common-name "My Release Key"
FlagDescription
--common-name NAMECertificate CN (default: Android Debug)
--key-size BITSRSA key size (default: 2048)

Verify a signature

sign-apk verify app-signed.apk
VALID (APK Signature Scheme v2+v3)
  v2 signer: CN=My Release Key
  v3 signer: CN=My Release Key (SDK 28-2147483647)

Exits with status 1 and prints INVALID: <reason> on failure, so it's usable directly in CI.

This is a self-check on this tool's own output, not a full reimplementation of Android's verifier (see Scope and limitations), and it doesn't cover v4 sidecars. To verify one of those, use apksigner instead:

apksigner verify --v4-signature-file app-signed.apk.idsig app-signed.apk

Scope and limitations

  • No v1 (JAR) signing, so APKs installing on Android 6 and below will not verify. v2 covers Android 7+, v3 Android 9+.
  • No key rotation. The v3 block is written with a single signer and no proof-of-rotation lineage, which is the common case for apps that are not rotating keys. Rotation (and the v3.1 block that targets it at a minimum SDK) is not implemented.
  • One signer per APK. The format allows several; this writes a single one.
  • Whole file is read into memory. Fine for typical APKs (an 89 MB app signs in about half a second), but not suited to very large files on memory-constrained machines.
  • No ZIP64. APKs above 4 GB, or with more than 65535 entries, are not supported.
  • sign-apk verify is a self-check, not a full reimplementation of Android's verifier. It checks v2 and v3 signatures and content digests, but does not validate certificate chains, expiry, SDK-range coverage across signers, rotation lineages, or v4 sidecars. Use apksigner verify when you need an authoritative answer.

Testing

python -m pytest tests/

The suite covers RSA and EC signing across v2/v3/v4, PKCS#12 and PEM loading, re-signing, content preservation, alignment, Merkle tree construction, and tamper detection. Where apksigner is on PATH, tests additionally assert that it accepts the output and rejects tampered files; those tests skip when it is absent.

One test asserts that this project's Merkle tree reproduces apksigner's byte for byte: given the same input file, the tree bytes and root hash match exactly.

Verified against Android apksigner on a real 89 MB production APK (originally v3-signed): all 1622 entries byte-identical after re-signing, zipalign -c -P 16 clean, v2 scheme: true on SDK 24-27, and v3 scheme: true / v4 scheme: true on SDK 28+.

How it works

Signing rewrites the APK into the layout the v2/v3 schemes require:

[ ZIP entries ]  [ APK Signing Block ]  [ Central Directory ]  [ EOCD ]
                          ^                                      ^
                   signature lives here          central-dir offset patched to match

Along the way it:

  • Strips stale signatures: old META-INF/*.SF / *.RSA / *.DSA / *.EC entries and any previous APK Signing Block (v2 or v3) are removed, so re-signing replaces rather than accumulates. MANIFEST.MF is left alone.
  • Never recompresses: entry payloads are copied verbatim, so contents and compression methods are bit-for-bit preserved.
  • Preserves alignment: uncompressed entries are re-emitted at 4-byte aligned offsets, and uncompressed .so libraries at 16 KB, matching zipalign -P 16. Android memory-maps these directly, so misalignment can break native library loading at runtime.

Key selection follows apksigner's rules: RSA keys under 3072 bits use SHA-256 (0x0103), larger ones SHA-512 (0x0104); EC keys use SHA-256 (0x0201) or SHA-512 (0x0202) by curve size.

References

The binary formats implemented here are specified by Android and PKWARE. Each module docstring cites the specification for the layout, constants, and algorithms it implements, along with the corresponding upstream file and function for cross-checking.

This project is an independent implementation written against those specifications. It is not affiliated with or endorsed by Google or PKWARE.

Contributors

adityatelange

4 commits

Languages

Python

100.0%