| name | crypto-recon-apk-java |
| description | Specialist for finding ALL cryptography in decompiled Android APK — Java source (jadx) and Smali bytecode (apktool). Covers OkHttp interceptors, Retrofit, HMAC, AES, MD5, custom signing, ProGuard-obfuscated code. |
Crypto Recon — APK Java/Smali Crypto Specialist
Companion Files
Read these alongside this SKILL.md for deep pattern catalogs:
java-patterns.md — BouncyCastle, Tink, Guava, custom hex, ProGuard-survived patterns, BuildConfig extraction
smali-patterns.md — Smali bytecode search commands, Smali → Python translation, ProGuard navigation
Purpose
Scan decompiled APK source (Java + Smali) for every cryptographic operation. Priority target: OkHttp/Retrofit interceptors that sign ALL API requests — equivalent of axios.interceptors in web.
Input
output/[apk_name]/java/ — jadx decompiled Java (readable, preferred)
output/[apk_name]/smali/ — apktool Smali (fallback when Java fails)
output/[apk_name]/class_index.txt — all class names
- HAR test vectors from
output/[apk_name]/har/ if available
Priority File List
Search these first — highest chance of crypto:
High priority class names:
*Interceptor*, *SignInterceptor*, *AuthInterceptor*
*Crypto*, *CryptoUtil*, *CryptoHelper*, *EncryptUtil*
*Sign*, *Signer*, *SignUtil*, *SignHelper*
*Security*, *SecurityUtil*, *SecurityManager*
*Hash*, *MD5*, *SHA*, *HMAC*
*ApiClient*, *HttpClient*, *NetworkUtil*
*RequestUtil*, *RequestBuilder*, *RequestHelper*
*Token*, *Auth*, *Session*
High priority package names:
*.network.*, *.http.*, *.api.*
*.utils.*, *.util.*, *.helper.*
*.security.*, *.crypto.*
Run priority scan first:
python tools/apk_scan.py output/[apk_name]/ --priority
Java Pattern Catalog
1. OkHttp Interceptors (HIGHEST VALUE)
OkHttp interceptors intercept ALL requests — this is where signing lives.
public class SignInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
return chain.proceed(signedRequest);
}
}
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request request = chain.request();
return chain.proceed(signed);
})
.build();
Request signed = original.newBuilder()
.header("X-Sign", generateSign(body))
.header("X-Timestamp", String.valueOf(System.currentTimeMillis() / 1000))
.header("X-Nonce", UUID.randomUUID().toString().replace("-", ""))
.build();
Search patterns (Java):
implements Interceptor
addInterceptor(
addNetworkInterceptor(
chain.request()
chain.proceed(
newBuilder().*header(
2. Hash Functions
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(data.getBytes("UTF-8"));
byte[] hash = md.digest();
String hex = bytesToHex(hash).toUpperCase();
MessageDigest.getInstance("SHA-1")
MessageDigest.getInstance("SHA-256")
MessageDigest.getInstance("SHA-512")
DigestUtils.md5Hex(data)
DigestUtils.sha256Hex(data)
org.apache.commons.codec.digest.DigestUtils
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
3. HMAC
Mac mac = Mac.getInstance("HmacMD5");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256");
mac.init(keySpec);
byte[] result = mac.doFinal(data.getBytes("UTF-8"));
String sig = Base64.encodeToString(result, Base64.NO_WRAP);
String sig = bytesToHex(result).toUpperCase();
4. AES Encryption
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8"));
String result = Base64.encodeToString(encrypted, Base64.NO_WRAP);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec paramSpec = new GCMParameterSpec(128, iv);
Extract verbatim: key source, IV source, mode, padding, output encoding.
5. RSA
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, Base64.DEFAULT));
PublicKey pubKey = kf.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted = cipher.doFinal(data.getBytes());
private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...";
6. Base64 & Encoding
Base64.encode(data, Base64.DEFAULT)
Base64.encode(data, Base64.NO_WRAP)
Base64.encode(data, Base64.URL_SAFE)
Base64.decode(str, Base64.DEFAULT)
java.util.Base64.getEncoder().encodeToString(data)
java.util.Base64.getDecoder().decode(str)
Integer.toHexString(b & 0xFF)
String.format("%02x", b)
String.format("%02X", b)
7. Signing Flow Patterns
Sort + concat pattern (common in Chinese apps):
Map<String, String> params = new TreeMap<>(requestParams);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (!entry.getKey().equals("sign") && entry.getValue() != null) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
String canonical = sb.toString();
String sign = MD5(canonical + secretKey).toUpperCase();
JSONObject sorted = new JSONObject(new TreeMap<>(paramsMap));
String json = sorted.toString();
String sign = MD5(json).toUpperCase().substring(0, 32);
Timestamp + nonce pattern:
long timestamp = System.currentTimeMillis() / 1000;
String nonce = UUID.randomUUID().toString().replace("-", "");
String nonce = String.valueOf((int)(Math.random() * 1000000));
8. Certificate Pinning (important to note)
CertificatePinner pinner = new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(...) {}
public void checkServerTrusted(...) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; }
}
};
Smali Pattern Catalog
Smali is Dalvik bytecode — use when Java decompile fails or is obfuscated.
Smali Hash Patterns
# MD5
const-string v0, "MD5"
invoke-static {v0}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;
invoke-virtual {v0, v1}, Ljava/security/MessageDigest;->update([B)V
invoke-virtual {v0}, Ljava/security/MessageDigest;->digest()[B
# SHA-256
const-string v0, "SHA-256"
invoke-static {v0}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;
# HMAC
const-string v0, "HmacSHA256"
invoke-static {v0}, Ljavax/crypto/Mac;->getInstance(Ljava/lang/String;)Ljavax/crypto/Mac;
invoke-virtual {v0, v1}, Ljavax/crypto/Mac;->init(Ljava/security/Key;)V
invoke-virtual {v0}, Ljavax/crypto/Mac;->doFinal()[B
Smali Cipher Patterns
# AES
const-string v0, "AES/CBC/PKCS5Padding"
invoke-static {v0}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;
invoke-virtual {v0, v1, v2}, Ljavax/crypto/Cipher;->init(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V
invoke-virtual {v0, v1}, Ljavax/crypto/Cipher;->doFinal([B)[B
# RSA
const-string v0, "RSA/ECB/PKCS1Padding"
invoke-static {v0}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;
Smali Base64 Patterns
# Android Base64
invoke-static {v0, v1}, Landroid/util/Base64;->encode([BI)[B
invoke-static {v0, v1}, Landroid/util/Base64;->decode([BI)[B
invoke-static {v0, v1}, Landroid/util/Base64;->encodeToString([BI)Ljava/lang/String;
Smali OkHttp Patterns
# Interceptor
invoke-virtual {v0, v1}, Lokhttp3/OkHttpClient$Builder;->addInterceptor(Lokhttp3/Interceptor;)Lokhttp3/OkHttpClient$Builder;
# Request builder
invoke-virtual {v0, v1, v2}, Lokhttp3/Request$Builder;->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;
invoke-virtual {v0, v1}, Lokhttp3/Request$Builder;->addHeader(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;
Analysis Steps
Step 1: Priority Class Scan
python tools/apk_scan.py output/[name]/ --priority
Lists all high-priority classes matching crypto/network patterns.
Step 2: Find Interceptors
Search for implements Interceptor in Java, or OkHttp patterns in Smali.
Read the FULL interceptor class — this is the signing entry point.
Step 3: Trace the Sign Function
From the interceptor, trace backwards to find the signing function:
SignInterceptor.intercept()
└── CryptoUtil.generateSign(params, timestamp)
├── sortParams(params) → TreeMap / sorted keys
├── buildCanonical(sorted) → concat / JSON
└── MD5(canonical + secret).upper() → signature
Step 4: Extract Every Parameter
- What fields are included? (field names)
- What fields are excluded? (null check, skip list)
- Sort order? (TreeMap = alphabetical, vs insertion order)
- Canonical format? (key=value&, JSON, custom)
- Hash algorithm and output format?
- Timestamp format? (ms or seconds)
- Nonce format? (UUID, random int, hex)
Step 5: Find Hardcoded Secrets
private static final String SECRET = "..."
private static final String APP_KEY = "..."
static final String SIGN_KEY = "..."
BuildConfig.API_SECRET
Also check assets/ folder — config.json, app.properties often contain keys.
Step 6: Check String Obfuscation
ProGuard/R8 obfuscates class/method names but NOT string values.
Search for crypto algorithm strings directly:
grep -r '"MD5"\|"HmacSHA256"\|"AES/CBC"\|"RSA"' output/[name]/smali/
Output Format
APK CRYPTO FINDINGS
===================
[APK1] OkHttp Signing Interceptor — HIGH
File: java/com/example/app/network/SignInterceptor.java
Class: SignInterceptor implements Interceptor
Line: 45
Signing flow:
timestamp = System.currentTimeMillis() / 1000
nonce = UUID.randomUUID().toString().replace("-", "")
body = request.body() → string
sorted = new TreeMap<>(params) ← alphabetical sort
canonical = buildCanonical(sorted) ← key=val& format
signature = MD5(canonical + SECRET_KEY).toUpperCase()[:32]
→ headers: X-Sign, X-Timestamp, X-Nonce
SECRET_KEY: HARDCODED "x7k9mNpQ2rSvWyZ3"
Callers: OkHttpClient.Builder.addInterceptor (ApiClient.java:23)
[APK2] AES Password Encryption — HIGH
File: java/com/example/app/utils/CryptoUtil.java
Class: CryptoUtil.encryptPassword(String)
Mode: AES/CBC/PKCS5Padding
Key: SHA256(userId + DEVICE_ID)[:32]
IV: HARDCODED "1234567890123456"
Output: Base64.NO_WRAP
[APK3] RSA Public Key — HIGH
File: java/com/example/app/security/RsaUtil.java
Mode: RSA/ECB/PKCS1Padding
Key: HARDCODED in source (see below)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----