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.
Live demo (browser, runs locally via WASM): https://agusibrahim.github.io/apksig-go/
| 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).
Existing options:
apksigner— Google's reference tool, but JVM-only and not embeddable.apksigJava lib — same constraint.
This port has:
- Minimal external dependencies — standard library plus two pure-Go
modules (
go-pkcs12,keystore-go) used only by the optionalpkg/keystoreloader. - WebAssembly support — runs in the browser, Node.js, Cloudflare Workers, embedded devices.
- CLI tools —
apksigverify,apksign,apksigverifyv4as drop-in replacements for the most commonapksignerflows. - Library API — embed in your own Go programs.
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 xzEach release also contains a pre-built WASM bundle (apksig-go_*_wasm.tar.gz)
ready to drop into a static site.
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@latestOr as a library:
go get github.com/agusibrahim/apksig-go@latestapksigverify -v app.apkAPK: 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
# 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.apkOr 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.apkpackage 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)
}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)
}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
.idsigverification.
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,
});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
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/- Go ≥ 1.23 (the
//go:debug x509negativeserial=1directive 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
- Certificates with negative serial numbers (Huawei, older vendor APKs)
via the
//go:debug x509negativeserial=1directive in command packages. - Certificates with non-standard
PrintableStringcharacters (@,/) via a lenient parser inpkg/x509util. The original DER is preserved so digests still match the on-disk certificate byte-for-byte. MANIFEST.MFreferencing entries missing from the ZIP — strict reject.- minSdk-aware verification rules using
pkg/axmlto readminSdkVersionfrom the binaryAndroidManifest.xml.
- v1 (JAR) signing for non-standard manifest entries with continuation
lines beyond what
keytoolproduces — the writer matchesapksigneroutput for typical Gradle builds. - JCEKS keystore format — convert to JKS or PKCS#12 with
keytool -importkeystorefirst. - Source stamp signing/verification.
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).