| name | crypto-recon-apk-debug |
| description | Verifies reconstructed APK signing logic against live app traffic. Uses HAR test vectors or ADB+mitmproxy. Extends crypto-recon-debugging for Android context. |
Crypto Recon — APK Debug & Verification
Companion Files
Read these alongside this SKILL.md:
java-debug-checklist.md — Systematic checklist: canonical format, hash algorithm, output encoding, encoding, sort order, field exclusions
frida-hooks.md — Frida runtime hooks to capture actual key + input + output from the running APK
Purpose
Test if the Python reconstruction of APK signing produces identical results to what the real app sends. Uses HAR test vectors if available, otherwise instructs user to capture live traffic.
Phase 1: Get Test Vector
Method A: From HAR (Preferred)
If HAR was already parsed:
output/[apk_name]/har/test-vectors.json
Use these directly — real input → real signature pairs.
Method B: Capture via mitmproxy + ADB
mitmproxy --mode transparent --listen-port 8080
adb shell settings put global http_proxy <your_ip>:8080
Method C: From Logcat
Some apps log signing info during debug:
adb logcat | grep -iE "sign|signature|token|nonce|timestamp"
Phase 2: Test Python Function
import sys
sys.path.insert(0, "output/[apk_name]/final")
from reconstruct_md5_sign import generate_sign
test_input = {
"userId": "12345",
"amount": "100",
"gameId": "5"
}
expected_sign = "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
result = generate_sign(test_input)
print(f"Expected: {expected_sign}")
print(f"Got: {result}")
print(f"Match: {'YES' if result == expected_sign else 'NO'}")
Phase 3: APK-Specific Debug Checklist
Android signing has specific differences from web:
[ ] String encoding: Java uses UTF-8 by default, Python too — but verify
[ ] TreeMap sort: Java TreeMap sorts by natural String order (same as Python sorted())
[ ] Timestamp: Java System.currentTimeMillis()/1000 → int, same as int(time.time())
[ ] UUID format: Java UUID without dashes → Python uuid4().hex matches
[ ] Base64 flag: NO_WRAP = no newlines → Python base64.b64encode, no '\n'
[ ] Byte encoding: Java getBytes("UTF-8") → Python .encode("utf-8")
[ ] Integer overflow: Java int is 32-bit → use ctypes if needed
[ ] BigDecimal: Java may format numbers differently → check toString()
[ ] JSON: Android JSONObject does NOT sort keys by default → check if TreeMap used first
[ ] Hex case: Java %02x = lowercase, %02X = uppercase → match Python accordingly
Phase 4: Root Cause on Mismatch
Follow systematic-debugging iron law. Binary search:
params = {"amount": "100", "gameId": "5", "nonce": "captured_nonce", "userId": "12345"}
params.pop("sign", None)
import json
sorted_params = dict(sorted(params.items()))
canonical_kv = "&".join(f"{k}={v}" for k, v in sorted_params.items())
canonical_json = json.dumps(sorted_params, separators=(',', ':'))
print(f"KV format: {repr(canonical_kv)}")
print(f"JSON format: {repr(canonical_json)}")
import hashlib
for fmt in [canonical_kv, canonical_json, canonical_kv + SECRET]:
md5 = hashlib.md5(fmt.encode()).hexdigest()
print(f"MD5({repr(fmt[:30])...}) = {md5} | {md5.upper()} | {md5[:32].upper()}")
Phase 5: Live API Test
If test vector matches, test against real API:
import cloudscraper
scraper = cloudscraper.create_scraper()
headers = {
"User-Agent": "MyApp/3.2.1 (Android 12; Samsung Galaxy S21)",
"Content-Type": "application/json",
"X-App-Version": "3.2.1",
}
resp = scraper.post("https://api.example.com/login", json=signed_payload, headers=headers)
print(resp.status_code, resp.text[:200])
Note: APK APIs often check User-Agent. Copy exact UA from HAR.
Output
Document in output/[apk_name]/final/[apk_name].md:
## Debug Log
Root cause: Android TreeMap sorts by String natural order.
Java sorts "amount" before "userId" → correct.
Python sorted() gives same order → verified.
Test vector:
input: {amount: "100", gameId: "5", userId: "12345"}
nonce: "a7f3b2c1d4e5f6a7"
expected: "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
got: "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
match: YES
Live test: HTTP 200, code=0 ✓