/apksigtool

apksigtool - parse/verify/clean/sign android apk (signing block)

Primary LanguagePythonGNU Affero General Public License v3.0AGPL-3.0

GitHub Release PyPI Version Python Versions CI AGPLv3+

apksigtool

parse/verify/clean android apk signing blocks & apks

apksigtool is a tool for parsing android APK Signing Blocks (either embedded in an APK or extracted as a separate file, e.g. using apksigcopier) and verifying APK signatures. It can also clean them (i.e. remove everything that's not an APK Signature Scheme v2/v3 Block or verity padding block), which can be useful for reproducible builds.

WARNING: verification is considered EXPERIMENTAL and SHOULD NOT BE RELIED ON, please use apksigner instead.

Parse

Parse tree (some output elided):

$ apksigtool parse some.apk
PAIR ID: 0x7109871a
  APK SIGNATURE SCHEME v2 BLOCK
  SIGNER 0
    SIGNED DATA
      DIGEST 0
        SIGNATURE ALGORITHM ID: 0x104 (RSASSA-PKCS1-v1_5 with SHA2-512 digest)
  [...]
  VERIFIED (1 signer(s))
PAIR ID: 0xf05368c0
  APK SIGNATURE SCHEME v3 BLOCK
  SIGNER 0
    SIGNED DATA
      DIGEST 0
        SIGNATURE ALGORITHM ID: 0x104 (RSASSA-PKCS1-v1_5 with SHA2-512 digest)
  [...]
  VERIFIED (1 signer(s))
PAIR ID: 0x42726577
  VERITY PADDING BLOCK

Extracted APKSigningBlock instead of APK:

$ mkdir meta
$ apksigcopier extract some.apk meta
$ apksigtool parse --block meta/APKSigningBlock
[...]

v1 (JAR) signature (some output elided):

$ apksigtool parse-v1 some.apk
JAR MANIFEST
  VERSION: 1.0
  CREATED BY: Android Gradle 7.1.3
  BUILT BY: Signflinger
JAR SIGNATURE FILE
  FILENAME: META-INF/CERT.SF
  VERSION: 1.0
  CREATED BY: Android Gradle 7.1.3
  SHA256 MANIFEST DIGEST: [...]
  ANDROID APK SIGNED: 2
JAR SIGNATURE BLOCK FILE
  FILENAME: META-INF/CERT.RSA
  CERTIFICATE
    [...]
  SIGNATURE
    VALUE (HEX): [...]
  HASH ALGORITHM: SHA256
VERIFIED (1 signature(s))

JSON

NB: elided binary values (digest, fingerprint, raw_data, signature) are represented as hex (e.g. foo would be represented as 666f6f).

$ apksigtool parse --json some.apk
full JSON output (long, some data elided)
{
  "_type": "APKSigningBlock",
  "pairs": [
    {
      "_type": "Pair",
      "id": 1896449818,
      "length": 1437,
      "value": {
        "_type": "APKSignatureSchemeBlock",
        "signers": [
          {
            "_type": "V2Signer",
            "public_key": {
              "_type": "PublicKey",
              "public_key_info": {
                "_type": "PublicKeyInfo",
                "algorithm": "RSA",
                "bit_size": 2048,
                "fingerprint": "[...]",
                "hash_algorithm": null
              },
              "raw_data": "[...]"
            },
            "signatures": [
              {
                "_type": "Signature",
                "algoritm_id_info": "RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks",
                "signature": "[...]",
                "signature_algorithm_id": 259
              }
            ],
            "signed_data": {
              "_type": "V2SignedData",
              "additional_attributes": [
                {
                  "_type": "AdditionalAttribute",
                  "id": 3203395597,
                  "is_proof_of_rotation_struct": false,
                  "is_stripping_protection": true,
                  "value": "03000000"
                }
              ],
              "certificates": [
                {
                  "_type": "Certificate",
                  "certificate_info": {
                    "_type": "CertificateInfo",
                    "fingerprint": "[...]",
                    "hash_algorithm": "SHA256",
                    "issuer": "Common Name: [...], Organizational Unit: [...]",
                    "not_valid_after": "2022-10-27 12:34:56+00:00",
                    "not_valid_before": "2022-10-26 12:34:56+00:00",
                    "serial_number": 42,
                    "signature_algorithm": "RSASSA_PKCS1V15",
                    "subject": "Common Name: [...], Organizational Unit: [...]"
                  },
                  "public_key_info": {
                    "_type": "PublicKeyInfo",
                    "algorithm": "RSA",
                    "bit_size": 2048,
                    "fingerprint": "[...]",
                    "hash_algorithm": null
                  },
                  "raw_data": "[...]"
                }
              ],
              "digests": [
                {
                  "_type": "Digest",
                  "algoritm_id_info": "RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks",
                  "digest": "[...]",
                  "signature_algorithm_id": 259
                }
              ],
              "raw_data": "[...]"
            }
          }
        ],
        "verification_error": null,
        "verified": 1,
        "version": 2
      }
    },
    {
      "_type": "Pair",
      "id": 4031998144,
      "length": 1437,
      "value": {
        "_type": "APKSignatureSchemeBlock",
        "signers": [
          {
            "_type": "V3Signer",
            "max_sdk": 2147483647,
            "min_sdk": 24,
            [...]
            "signed_data": {
              [...]
              "max_sdk": 2147483647,
              "min_sdk": 24,
              [...]
            }
          }
        ],
        "verification_error": null,
        "verified": 1,
        "version": 3
      }
    },
    {
      "_type": "Pair",
      "id": 1114793335,
      "length": 1166,
      "value": {
        "_type": "VerityPaddingBlock"
      }
    }
  ]
}

To extract e.g. pair types or IDs:

$ apksigtool parse --json some.apk | jq -r '.pairs[].value._type'
APKSignatureSchemeBlock
APKSignatureSchemeBlock
VerityPaddingBlock
$ apksigtool parse --json some.apk | jq -r '.pairs[].id' | awk '{printf "0x%x\n", $1}'
0x7109871a
0xf05368c0
0x42726577

To extract e.g. public key info:

$ apksigtool parse --json some.apk | jq '.pairs[].value.signers[]?.public_key.public_key_info'
{
  "_type": "PublicKeyInfo",
  "algorithm": "RSA",
  "bit_size": 2048,
  "fingerprint": "[...]",
  "hash_algorithm": null
}
[...]

To extract e.g. certificate info:

$ apksigtool parse --json some.apk | jq '.pairs[].value.signers[]?.signed_data.certificates[].certificate_info'
{
  "_type": "CertificateInfo",
  "fingerprint": "[...]",
  "hash_algorithm": "SHA256",
  "issuer": "Common Name: [...], Organizational Unit: [...]",
  "not_valid_after": "2022-10-27 12:34:56+00:00",
  "not_valid_before": "2022-10-26 12:34:56+00:00",
  "serial_number": 42,
  "signature_algorithm": "RSASSA_PKCS1V15",
  "subject": "Common Name: [...], Organizational Unit: [...]"
}
[...]

v1 (JAR) signature:

$ apksigtool parse-v1 --json some.apk | jq -r .manifest.created_by
Android Gradle 7.1.3

Verify

WARNING: verification is considered EXPERIMENTAL and SHOULD NOT BE RELIED ON, please use apksigner instead.

$ apksigtool verify some.apk
WARNING: verification is considered EXPERIMENTAL, please use apksigner instead.
v2 verified (1 signer(s))
v3 verified (1 signer(s))
$ apksigtool verify-v1 some.apk
WARNING: verification is considered EXPERIMENTAL, please use apksigner instead.
v1 verified (1 signature(s))
Warning: rollback protections require v2, v3 signature(s) as well.

Clean

NB: modifies in place!

$ cp some.apk cleaned.apk
$ apksigtool clean cleaned.apk
cleaned
$ apksigtool clean cleaned.apk
nothing to clean

Use --check to get errors when parsing or verification (when not using --block) fails:

$ cp some.apk cleaned.apk
$ apksigtool clean --check cleaned.apk
[...]

Extracted APKSigningBlock instead of APK:

$ mkdir meta
$ apksigcopier extract some.apk meta
$ apksigtool clean --block meta/APKSigningBlock
cleaned

Help

$ apksigtool --help
$ apksigtool parse --help       # verify --help, clean --help, etc.

Python API

APK Signing Block

>>> import apksigtool
>>> _, data = apksigtool.extract_v2_sig(apk)
>>> blk = apksigtool.APKSigningBlock.parse(data)    # parse APK Signing Block
>>> blk = apksigtool.parse_apk_signing_block(data)  # same as above

>>> apksigtool.show_parse_tree(blk)                 # print parse tree
>>> apksigtool.show_json(blk)                       # JSON

>>> blk.verify(apk)                                 # [EXPERIMENTAL] raises on failure
>>> result = verified, failed = blk.verify_results(apk)
>>> result = apksigtool.verify_apk(apk)             # uses .verify_results()

Cleaning

>>> import apksigtool
>>> _, data = apksigtool.extract_v2_sig(apk)
>>> data_cleaned = apksigtool.clean_apk_signing_block(data)

>>> apksigtool.clean_apk(some_apk)                  # NB: modifies existing APK!

v1 (JAR) signatures

>>> import apksigcopier, apksigtool
>>> meta = tuple(apksigcopier.extract_meta(apk))
>>> sig = apksigtool.JARSignature.parse(meta)       # parse v1 signature
>>> sig = apksigtool.parse_apk_v1_signature(meta)   # same as above

>>> apksigtool.show_v1_signature(sig)               # print parse tree
>>> apksigtool.show_json(sig)                       # JSON

>>> result = sig.verify(apk)                        # [EXPERIMENTAL] raises on failure

Tab Completion

NB: the syntax for the environment variable changed in click >= 8.0, use e.g. source_bash instead of bash_source for older versions.

For Bash, add this to ~/.bashrc:

eval "$(_APKSIGTOOL_COMPLETE=bash_source apksigtool)"

For Zsh, add this to ~/.zshrc:

eval "$(_APKSIGTOOL_COMPLETE=zsh_source apksigtool)"

For Fish, add this to ~/.config/fish/completions/apksigtool.fish:

eval (env _APKSIGTOOL_COMPLETE=fish_source apksigtool)

Installing

From git

NB: this installs the latest development version, not the latest release.

$ git clone https://github.com/obfusk/apksigtool.git
$ cd apksigtool
$ pip install -e .

NB: you may need to add e.g. ~/.local/bin to your $PATH in order to run apksigtool.

To update to the latest development version:

$ cd apksigtool
$ git pull --rebase

Dependencies

  • Python >= 3.8 + apksigcopier + asn1crypto + click + cryptography + pyasn1 + pyasn1-modules + simplejson.

Debian/Ubuntu

$ apt install apksigcopier python3-{asn1crypto,click,cryptography,pyasn1{,-modules},simplejson}

License

AGPLv3+