Skip to content

Quickstart

This page does the same two things with each interface: print basic information about an APK, then list its signing certificates. The examples use apkstest.apk, the small test app from the repository, but any .apk, .xapk, .apkm or .apks file works. To follow along:

unzip -p core/tests/fixtures/apks-universal.apks universal.apk > apkstest.apk

The CLI on a malware sample, a ToxicPanda banker:

$ apk-info show --signatures --anomalies malware.apk
malware.apk
  Package         com.fmvmxi.qvhmylhz
  Label           Xvide
  Version         1.1 (1)
  SDK             min 26 · target 33
  Main activity   -

Signatures
  v2, v3          CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown
    Serial        14cbf84a
    Valid         2018-03-17 → 2045-08-02
    Algorithm     sha256WithRSAEncryption
    SHA-256       f67430285c020c72fbeaa81870b42c79bcc159b08be2ddd83789d0eae68215fd
    SHA-1         520a87add5f61b426b7458ef05655267853c008d
    MD5           0856d66bfce9027a146f9ba03fe1e9b9

Tampering         YES · 2 tampered
  ✗ encryption_flag            3  AndroidManifest.xml, resources.arsc, … 1 more
  ✗ string_pool_garbage_chunk  2  AndroidManifest.xml, resources.arsc

Basic information

$ apk-info show apkstest.apk
apkstest.apk
  Package         io.github.delvinru.apkstest
  Label           APKS Test
  Version         1.0.7 (7)
  SDK             min 21 · target 35
  Main activity   io.github.delvinru.apkstest/.MainActivity
  ABIs            x86_64

show accepts several files and directories at once and walks directories recursively.

from apk_info import APK

apk = APK(path)

print(f"Package:     {apk.get_package_name()}")
print(f"Version:     {apk.get_version_name()}")
print(f"Min SDK:     {apk.get_min_sdk_version()}")
print(f"Target SDK:  {apk.get_target_sdk_version()}")
print(f"Label:       {apk.get_application_label()}")
print(f"Main:        {apk.get_main_activity()}")

Getters return None when the manifest doesn't set the value.

let apk = Apk::new(&path)?;

println!(
    "Package:     {}",
    apk.get_package_name().unwrap_or_default()
);
println!(
    "Version:     {}",
    apk.get_version_name().unwrap_or_default()
);
println!(
    "Min SDK:     {}",
    apk.get_min_sdk_version().unwrap_or_default()
);
println!("Target SDK:  {}", apk.get_target_sdk_version());
println!(
    "Label:       {}",
    apk.get_application_label().unwrap_or_default()
);
println!(
    "Main:        {}",
    apk.get_main_activity().unwrap_or_default()
);

Getters return Option, so a missing manifest attribute is None rather than an error.

The Python and Rust examples print:

Package:     io.github.delvinru.apkstest
Version:     1.0.7
Min SDK:     21
Target SDK:  35
Label:       APKS Test
Main:        .MainActivity

The CLI prints the main activity as a package/activity component name, ready for adb shell am start -n. The library returns the activity name as written in the manifest, which may be relative (.MainActivity).

Signatures

$ apk-info show --signatures apkstest.apk
...
APK Signature block:
  Type: v1
  Serial Number: 1bdde6531ed48e6b
  Subject: CN=apk-info apks test
  Issuer: CN=apk-info apks test
  Valid from: 2026-10-31T18:17:58Z
  Valid until: 2126-10-07T18:17:58Z
  Signature type: sha384WithRSAEncryption
  MD5 fingerprint: 19c9134e6e59d32b357a88a77fca527d
  SHA1 fingerprint: 5a1a310e23b7a1e074be7772595c6c19d22f8b60
  SHA256 fingerprint: 71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2

  Type: v2
  ...
from apk_info import Signature

for signature in apk.get_signatures():
    match signature:
        case Signature.V1() | Signature.V2() | Signature.V3() | Signature.V31() | Signature.V32():
            for cert in signature.certificates:
                print(f"{type(signature).__name__}: {cert.subject} ({cert.sha256_fingerprint})")
        case _:
            print(signature)

Each scheme has its own class (Signature.V1, Signature.V2, …), so you can match on it.

V1: CN=apk-info apks test (71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2)
V2: CN=apk-info apks test (71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2)
V3: CN=apk-info apks test (71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2)
for signature in apk.get_signatures()? {
    match &signature {
        Signature::V1(certs)
        | Signature::V2(certs)
        | Signature::V3(certs)
        | Signature::V31(certs)
        | Signature::V32(certs) => {
            for cert in certs {
                println!(
                    "{}: {} ({})",
                    signature.name(),
                    cert.subject,
                    cert.sha256_fingerprint
                );
            }
        }
        other => println!("{}", other.name()),
    }
}

Signature::name() gives a short scheme name (v1, v3.1, Stamp Block v2, …).

v1: CN=apk-info apks test (71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2)
v2: CN=apk-info apks test (71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2)
v3: CN=apk-info apks test (71c1bcf09db2254f8eadf5f68637f84f5c34acc04fe9c672393c518af68bc6f2)

Build tools sign most APKs with several schemes and one key, so the same certificate shows up once per scheme. For split containers, get_signatures() returns the base APK's signatures, and get_container_signatures() returns the signature of the outer archive, if it has one.

JSON output

For scripts, show --json prints one JSON object per APK (JSONL):

$ apk-info show --json apkstest.apk | jq '{package_name, version_name, min_sdk_version}'
{
  "package_name": "io.github.delvinru.apkstest",
  "version_name": "1.0.7",
  "min_sdk_version": "21"
}

Run the examples

Both examples live in the repository, and CI runs them on every change:

python examples/quickstart.py apkstest.apk
cargo run --example quickstart -- apkstest.apk

Next steps

  • CLI reference: extraction, manifest decoding, repacking BadPack samples.
  • Python API: permissions, components, resources and more.