Skip to content

agusibrahim/apksig-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

apksig-go

A pure-Go port of Android's apksig library, with a WebAssembly target so you can verify and sign APKs in the browser without uploading anything.

Go Reference License: Apache-2.0 CI

Live demo (browser, runs locally via WASM): https://agusibrahim.github.io/apksig-go/

What it does

Scheme Verify Sign
v1 (JAR signing)
v2 (APK Signature Scheme v2)
v3 (APK Signature Scheme v3)
v3.1 (key rotation, SDK ≥ 33)
v4 (.idsig, fs-verity)
v4.1 (dual-signer .idsig)
SigningCertificateLineage
zipalign (4-byte entry alignment)
JKS / PKCS#12 keystore input

Verified APKs signed by this library are accepted byte-for-byte by Google's upstream apksigner reference tool. Verification has been cross-validated against apksigner on 100+ real APKs (tests/e2e).

Why

Existing options:

  • apksigner — Google's reference tool, but JVM-only and not embeddable.
  • apksig Java lib — same constraint.

This port has:

  • Minimal external dependencies — standard library plus two pure-Go modules (go-pkcs12, keystore-go) used only by the optional pkg/keystore loader.
  • WebAssembly support — runs in the browser, Node.js, Cloudflare Workers, embedded devices.
  • CLI toolsapksigverify, apksign, apksigverifyv4 as drop-in replacements for the most common apksigner flows.
  • Library API — embed in your own Go programs.

Install

Pre-built binaries (recommended)

Download a release tarball from the Releases page for your OS/arch:

# Linux x86_64
curl -L https://github.com/agusibrahim/apksig-go/releases/latest/download/apksig-go_linux_amd64.tar.gz | tar xz

# macOS Apple Silicon
curl -L https://github.com/agusibrahim/apksig-go/releases/latest/download/apksig-go_darwin_arm64.tar.gz | tar xz

Each release also contains a pre-built WASM bundle (apksig-go_*_wasm.tar.gz) ready to drop into a static site.

From source

go install github.com/agusibrahim/apksig-go/cmd/apksigverify@latest
go install github.com/agusibrahim/apksig-go/cmd/apksign@latest
go install github.com/agusibrahim/apksig-go/cmd/apksigverifyv4@latest

Or as a library:

go get github.com/agusibrahim/apksig-go@latest

Quick start

Verify an APK

apksigverify -v app.apk
APK: app.apk (12426276 bytes)
Verified: true
  v3.1: present=false verified=false
  v3:   present=true  verified=true
  v2:   present=true  verified=true
  v1:   verified=true

Sign an APK

# Generate a self-signed key+cert (or bring your own)
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
  -nodes -days 10950 -subj "/CN=Android Test/O=Example/C=US"

# Sign with v1 + v2 + v3 + v3.1 + v4 (.idsig) and 4-byte zipalign
apksign -key key.pem -cert cert.pem \
        -v1 -v3.1 -v4 -align \
        -in unsigned.apk -out signed.apk

Or sign directly from a JKS/PKCS#12 keystore created by Android Studio or keytool:

apksign -keystore release.jks -storepass env:KS_PASS -alias upload \
        -v1 -v3.1 -align \
        -in unsigned.apk -out signed.apk

-storepass and -keypass accept a literal password, env:NAME to pull from an environment variable, or file:PATH to read from a file — useful for keeping passwords out of shell history. -alias defaults to the first key entry in the store.

This produces both signed.apk and signed.apk.idsig. Cross-check with Google's apksigner if you have the Android SDK installed:

apksigner verify --print-certs --min-sdk-version 24 signed.apk

Library usage

package main

import (
    "fmt"
    "os"

    "github.com/agusibrahim/apksig-go/pkg/apkverifier"
    "github.com/agusibrahim/apksig-go/pkg/datasource"
)

func main() {
    f, _ := os.Open("app.apk")
    defer f.Close()
    st, _ := f.Stat()
    ds := datasource.NewReaderAt(f, st.Size())
    res, _ := apkverifier.Verify(ds, 24, 35)
    fmt.Printf("verified=%v v1=%v v2=%v v3=%v\n",
        res.Verified, res.V1Verified, res.V2Verified, res.V3Verified)
}

Signing programmatically

package main

import (
    "crypto/x509"
    "encoding/pem"
    "os"

    "github.com/agusibrahim/apksig-go/pkg/algo"
    "github.com/agusibrahim/apksig-go/pkg/apkwriter"
    "github.com/agusibrahim/apksig-go/pkg/datasource"
    "github.com/agusibrahim/apksig-go/pkg/signer"
)

func main() {
    keyPEM, _ := os.ReadFile("key.pem")
    certPEM, _ := os.ReadFile("cert.pem")

    keyBlock, _ := pem.Decode(keyPEM)
    priv, _ := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
    certBlock, _ := pem.Decode(certPEM)
    cert, _ := x509.ParseCertificate(certBlock.Bytes)

    alg, _ := algo.PickAlgorithm(priv)
    cfg := &signer.SignerConfig{
        PrivateKey: priv,
        Certs:      []*x509.Certificate{cert},
        Algorithms: []algo.Algorithm{alg},
    }

    apkBytes, _ := os.ReadFile("unsigned.apk")
    out, _ := os.Create("signed.apk")
    defer out.Close()

    w := &apkwriter.SignedAPKWriter{
        Src:       datasource.NewBytes(apkBytes),
        Signers:   []*signer.SignerConfig{cfg},
        V3MinSdk:  28,
        V3MaxSdk:  0x7fffffff,
    }
    w.Write(out)
}

WebAssembly

Build:

GOOS=js GOARCH=wasm go build -o web/apksig.wasm ./wasm
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" web/

Serve web/ over HTTP and open index.html. The demo includes:

  • APK verification (drag a file in, see all schemes verified locally, including 4KB native-library alignment status).
  • APK signing (paste a key + cert PEM or upload a JKS/PKCS#12 keystore with a password, download the signed APK).
  • Key/certificate generator (RSA-2048, RSA-4096, or ECDSA P-256).
  • v4 .idsig verification.

JavaScript API:

// Verify
const result = apksigVerify(apkBytes, { minSdk: 24, maxSdk: 35 });

// Sign with PEM key + cert
const { signedApk, idsig } = apksigSign(apkBytes, keyPEM, certPEM, {
  v1: false, v3: true, v31: false, v4: false, align: false,
  v3MinSdk: 28, v31MinSdk: 33,
});

// Sign with a JKS or PKCS#12 keystore (raw bytes)
const r = apksigSignKeystore(apkBytes, keystoreBytes, {
  storePass: "changeit",
  keyPass: "",      // optional, defaults to storePass
  alias: "",        // optional, defaults to first key entry
  v1: false, v3: true, v31: false, v4: false, align: false,
});

// Verify v4 .idsig
const v4 = apksigVerifyV4(apkBytes, idsigBytes);

// Generate a new self-signed key + cert
const { keyPEM, certPEM } = apksigGenerateKey({
  commonName: "Android Test", org: "Example", country: "US",
  validityDays: 10950, keyType: "rsa", rsaBits: 2048,
});

Architecture

pkg/
├── apkverifier/    # Orchestrator: tries v3.1 → v3 → v2 → v1
├── verifier/{v1,v2,v3,v4}/
├── apkwriter/      # Streams a re-signed APK
├── signer/         # Builds v2/v3/v3.1 signing block payloads
├── v1signer/       # Builds META-INF (MANIFEST.MF / *.SF / *.RSA) for v1
├── v4signer/       # Builds .idsig files
├── keystore/       # JKS and PKCS#12 keystore loader (auto-detect)
├── apksigblock/    # APK Signing Block parser
├── digest/         # Chunked SHA-256/512 + verity Merkle tree
├── algo/           # Signature algorithm table (RSA-PKCS1, RSA-PSS, ECDSA, DSA)
├── pkcs7/          # PKCS#7 SignedData (DER) for v1
├── jarmanifest/    # MANIFEST.MF / .SF parser
├── lineage/        # SigningCertificateLineage (proof-of-rotation)
├── x509util/       # Lenient X.509 parser (handles Huawei/legacy quirks)
├── axml/           # Binary AndroidManifest.xml parser (extract minSdkVersion)
├── zip/            # ZIP / EOCD / CD parsing + zipalign helpers
├── datasource/     # io.ReaderAt-based view abstraction (WASM-safe)
└── buf/            # Length-prefixed slice helpers

cmd/
├── apksigverify/   # CLI verifier
├── apksign/        # CLI signer (v1/v2/v3/v3.1/v4, JKS/P12 input, zipalign)
├── apksigverifyv4/ # CLI for .idsig verification
├── certinfo/       # Print signer cert fingerprints
└── axmldump/       # Debug AndroidManifest.xml dump

wasm/               # syscall/js bindings (build with GOOS=js GOARCH=wasm)
web/                # HTML demo: index.html + apksig.wasm + wasm_exec.js
testdata/apk/       # Real APK fixtures for end-to-end tests
testdata/keystore/  # JKS / PKCS#12 fixtures for pkg/keystore tests
tests/e2e/          # Integration tests including apksigner cross-check

Testing

go test ./...

The tests/e2e/TestCrossValidate_Apksigner test compares this port's output against Google's reference apksigner if it is on PATH (skipped otherwise). A cross-validation script is also provided:

./tools/cross-validate.sh /path/to/apks/

Compatibility

  • Go ≥ 1.23 (the //go:debug x509negativeserial=1 directive used by the CLIs requires Go 1.23+; library packages alone work on earlier versions)
  • WASM target: any modern browser, Node.js ≥ 16
  • Tested on darwin/arm64; no platform-specific code

Quirks handled

  • Certificates with negative serial numbers (Huawei, older vendor APKs) via the //go:debug x509negativeserial=1 directive in command packages.
  • Certificates with non-standard PrintableString characters (@, /) via a lenient parser in pkg/x509util. The original DER is preserved so digests still match the on-disk certificate byte-for-byte.
  • MANIFEST.MF referencing entries missing from the ZIP — strict reject.
  • minSdk-aware verification rules using pkg/axml to read minSdkVersion from the binary AndroidManifest.xml.

Intentionally not implemented

  • v1 (JAR) signing for non-standard manifest entries with continuation lines beyond what keytool produces — the writer matches apksigner output for typical Gradle builds.
  • JCEKS keystore format — convert to JKS or PKCS#12 with keytool -importkeystore first.
  • Source stamp signing/verification.

License

Apache-2.0. See LICENSE.

This is a clean-room port, but the design closely follows the upstream apksig algorithms and constants. The original Java apksig source code is also Apache-2.0 licensed (Copyright The Android Open Source Project).

About

Pure-Go port of Android's apksig — verify and sign APKs (v1/v2/v3/v3.1/v4), runs in browser via WASM

Topics

Resources

License

Stars

Watchers

Forks

Contributors