SecureVar is a production-grade Android security library that provides re-sealable secure variables with server-authorized write control. Variables are protected by multiple cryptographic layers and can only be modified with time-limited, one-time-use write keys issued by your backend.
Add the Maven Central dependency to your app's build.gradle.kts:
// app/build.gradle.kts
dependencies {
implementation("io.github.mohammedalaamorsi:securevar:0.0.3")
}Traditional variable protection approaches use read-only properties or obfuscation. SecureVar takes a different approach:
- Variables are writable, but only through cryptographically validated authorization
- Server controls all writes via time-limited, one-time-use WriteKeys
- Multi-layer protection prevents tampering, replay attacks, and unauthorized modifications
- Zero-trust architecture assumes the client is compromised and validates everything
- Runtime threat detection actively monitors for root, hooking, and debugging
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Application Layer β
β ββββββββββββββ βββββββββββββββββββ β
β β SessionMgr ββββββββββββββββββββββββ SecureVar<T> β β
β ββββββββββββββ βββββββββββββββββββ β
β β β β
β β authorizedWrite(value, WriteKey) β β
β ββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Authorization Layer (WriteKey) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β’ Nonce (one-time use) + Timestamp + TTL β β
β β β’ HMAC-SHA256 or ECDSA signature β β
β β β’ Bound to: userId, propertyName, scope β β
β β β’ Replay prevention via nonce store + MAC β β
β β β’ Risk posture enforcement (debugger/root/hook) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Sealing Layer (SecureVarDelegate) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β’ AES-GCM-128 encryption (per-instance key) β β
β β β’ HMAC-SHA256 MAC (propertyName:salt:IV:cipher) β β
β β β’ Checksum fallback β β
β β β’ Obfuscation (split + noise) β β
β β β’ Per-instance salt (prevents cross-instance reuse) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Runtime Protection Layer β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β’ OriginVerifier: stack trace + ClassLoader + APK β β
β β signature + call depth + caller method checks β β
β β β’ RiskDetector: root/hook/emulator/debugger scan β β
β β β’ SecureMemory: plaintext wiping + GC hints β β
β β β’ Rate limiting (10 writes/min per variable) β β
β β β’ Tamper detection & alerts β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Secret Management Layer β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β’ Per-install random secrets (MAC + ENC) β β
β β β’ Google Tink AEAD encryption β β
β β β’ Android Keystore backing β β
β β β’ DataStore with encrypted storage β β
β β β’ Dynamic secret provisioning via SecretProvider β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- One-time use: Nonces tracked and rejected on replay
- Time-limited: TTL enforcement with configurable clock skew tolerance
- Cryptographically signed: HMAC-SHA256 or ECDSA (asymmetric preferred)
- Context-bound: Signatures include userId, propertyName, and scope
- Risk-aware: High-risk environments (debugger/root) require asymmetric signatures
- AES-GCM encryption: 128-bit authentication tag, unique IV per write
- Per-instance keys: Derived from
SHA-256(encSecret:propertyName:instanceSalt) - MAC protection: HMAC-SHA256 over
propertyName:instanceSalt:IV:ciphertext - Tamper detection: MAC + checksum dual verification
- Obfuscation: Split value + random noise for additional depth
- Nonce store: Persistent encrypted SharedPreferences
- Integrity MAC: HMAC over canonical nonce list (detects store tampering)
- Automatic cleanup: Expired nonces pruned on validation
| Category | Checks |
|---|---|
| Root detection | SU binaries, Magisk, KernelSU, root build tags, suspicious system properties, mount namespace cloaking, SELinux permissive mode, Zygisk/Shamiko DenyList artifacts, /proc access tampering, native property reading (bypasses hooked getprop) |
| Hook detection | Frida port scanning (default + range 27000β27100 with D-Bus fingerprinting), /proc/maps agent library scanning, Frida gadget class loading, Xposed/EdXposed/LSPosed class detection, Substrate detection, native hook libraries, thread name scanning (/proc/self/task/*/comm for gmain, gdbus, gum-js-loop), suspicious file descriptor scanning (/proc/self/fd), process name integrity check |
| Emulator detection | Build fingerprint, hardware, manufacturer, model, and product heuristics |
| Debugger detection | Debug.isDebuggerConnected(), TracerPid in /proc/self/status, ApplicationInfo.FLAG_DEBUGGABLE |
| Package scanning | Detects installed root managers, hooking frameworks, and reverse engineering tools |
- Stack trace analysis: Validates calling package prefixes
- ClassLoader validation: Detects custom/injected ClassLoaders
- APK signature pinning: SHA-256 signing certificate verification
- Call depth validation: Rejects calls with abnormal stack depth (spoofed stacks)
- Caller method verification: Requires specific method names in the call chain
- String wiping: Reflection-based zeroing of
Stringbackingbyte[]/char[]arrays - Byte/Char array wiping: Direct zero-fill utilities
- SecureScope: Auto-wipes all tracked secrets on scope exit
- GC hints: Triggers garbage collection after wipe to reduce plaintext window
- PeriodicWiper: Background daemon thread sweeps globally tracked weak references every 30 seconds
- Framework-agnostic: Works with
HttpsURLConnection, OkHttp, Ktor, and others - SHA-256 SPKI pinning: Validates server certificate public key hashes
- Backup pins: Supports certificate rotation without breaking existing installs
- Subdomain support: Optional
*.hostnamematching - Pin computation helper:
computePin()for discovering pins during development
- Per-install secrets: Random 32-byte Base64 secrets generated once
- Encrypted storage: Google Tink AEAD with AES256-GCM
- Hardware-backed: Android Keystore integration
- DataStore: Modern async preferences with encryption layer
- SecretProvider: Runtime interface for MAC/ENC secret retrieval
class SecureVarApplication : Application() {
private val dataStore by preferencesDataStore(name = "securevar_secrets")
override fun onCreate() {
super.onCreate()
// Initialize Tink encryption
EncryptedDataStore.initialize(this)
// Start periodic memory wiper (recommended)
SecureMemory.PeriodicWiper.start()
// Initialize SecureVar with encrypted secret provider
SecureVarManager.initialize(
SecureVarConfig(
action = SecureVarAction.Alert("https://your-backend.com/security/alert"),
secretProvider = object : SecretProvider {
override fun getMacSecret(): String = getOrCreateSecret("mac_secret")
override fun getEncSecret(propertyName: String): String = getOrCreateSecret("enc_secret")
private fun getOrCreateSecret(key: String): String = runBlocking {
val prefKey = stringPreferencesKey(key)
val encrypted = dataStore.data.map { it[prefKey] }.first()
if (encrypted != null) {
return@runBlocking EncryptedDataStore.decrypt(encrypted, this@SecureVarApplication)
}
val bytes = ByteArray(32)
java.security.SecureRandom().nextBytes(bytes)
val plaintext = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
val encryptedValue = EncryptedDataStore.encrypt(plaintext, this@SecureVarApplication)
dataStore.edit { it[prefKey] = encryptedValue }
plaintext
}
},
originVerifier = OriginVerifier.Builder(this)
.allowPackage("io.mohammedalaamorsi")
.pinSignature("SHA-256:AB:CD:...")
.expectCallDepth(5..30)
.expectCallerMethod("authorizedWrite")
.build(),
context = this,
onRiskDetected = { report ->
Log.w("SecureVar", "Risk detected: ${report.details}")
// Handle: logout, wipe data, notify server, etc.
},
writeKeyVerifier = { writeKey ->
WriteKeyValidator.validate(writeKey, this).let { result ->
when (result) {
is WriteKeyValidator.ValidationResult.Valid -> {
WriteKeyValidator.markNonceUsed(writeKey, this)
true
}
else -> false
}
}
}
)
)
}
}class SessionManager {
private val isPremiumUserDelegate = SecureVarDelegate(
initialValue = false,
propertyName = "isPremiumUser"
)
var isPremiumUser: Boolean
get() = isPremiumUserDelegate.getValue(this, ::isPremiumUser)
private set(value) {
// Direct assignment is FORBIDDEN and will be ignored
isPremiumUserDelegate.setValue(this, ::isPremiumUser, value)
}
// Authorized write method - ONLY way to update secure variables
suspend fun upgradeUserToPremium(userId: String) {
val writeKey = UserApi.requestPremiumUpgrade(userId)
isPremiumUserDelegate.authorizedWrite(
newValue = true,
key = writeKey
)
}
}// Backend API example (Node.js/Express)
app.post('/api/writekey/premium-upgrade', async (req, res) => {
const { userId } = req.body;
if (!await canUpgradeToPremium(userId)) {
return res.status(403).json({ error: 'Unauthorized' });
}
const nonce = crypto.randomBytes(16).toString('hex');
const timestamp = Date.now();
const ttlMillis = 5 * 60 * 1000; // 5 minutes
const propertyName = 'isPremiumUser';
const scope = 'premium_upgrade';
const message = `${nonce}:${timestamp}:${userId}:${propertyName}:${scope}`;
const hmacSignature = crypto
.createHmac('sha256', process.env.APP_SECRET_KEY)
.update(message)
.digest('base64');
// Optional: ECDSA for high-security
const sign = crypto.createSign('SHA256');
sign.update(message);
const asymSignature = sign.sign(privateKey, 'base64');
res.json({
nonce, timestamp, signature: hmacSignature, asymSignature,
ttlMillis, userId, propertyName, scope
});
});object UserApi {
suspend fun requestPremiumUpgrade(userId: String): WriteKey {
val response = httpClient.post("https://your-backend.com/api/writekey/premium-upgrade") {
setBody(json { "userId" to userId })
}
val data = response.body<JsonObject>()
return WriteKey(
nonce = data["nonce"].asString,
timestamp = data["timestamp"].asLong,
signature = data["signature"]?.asString,
asymSignature = data["asymSignature"]?.asString,
ttlMillis = data["ttlMillis"]?.asLong ?: 300_000L,
userId = data["userId"]?.asString,
propertyName = data["propertyName"]?.asString,
scope = data["scope"]?.asString
)
}
}val pinConfig = CertificatePinning.PinConfig(
hostname = "api.yourapp.com",
sha256Pins = setOf("sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="),
backupPins = setOf("sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
)
// With HttpsURLConnection
val connection = URL("https://api.yourapp.com/writekey").openConnection() as HttpsURLConnection
CertificatePinning.apply(connection, pinConfig)
// With OkHttp
val okPinner = okhttp3.CertificatePinner.Builder()
.add(pinConfig.hostname, *pinConfig.allPins.toTypedArray())
.build()
val client = OkHttpClient.Builder().certificatePinner(okPinner).build()// Wipe a single string after use
val secret = decryptSomething()
useSecret(secret)
SecureMemory.wipeString(secret)
// Auto-wipe everything in a scope
SecureMemory.withSecureScope { scope ->
val token = scope.track(decryptToken())
val key = scope.track(decryptKey())
// ... use token and key ...
} // Both wiped automatically + GC hint
// Track long-lived secrets for periodic cleanup
SecureMemory.PeriodicWiper.trackGlobal(sensitiveString)// Quick check
if (RiskDetector.isHighRisk(context)) {
// Respond: logout, wipe data, alert server
}
// Detailed report
val report = RiskDetector.getDetailedRiskReport(context)
if (report.highRisk) {
Log.w("Security", "Threats: ${report.details}")
// e.g., ["root:magisk_detected", "hook:frida_thread_detected", "root:selinux_permissive"]
}SecureVarManager.initialize(
SecureVarConfig(
action = SecureVarAction.Logout, // Logout user on tamper detection
secretProvider = mySecretProvider
)
)class SecureVarDelegate<T>(
initialValue: T,
propertyName: String,
private val writeLimitPerMinute: Int = 20 // Custom limit
) : ReadWriteProperty<Any?, T> {
// ... implementation
}./gradlew :securevar:test./gradlew :securevar:connectedDebugAndroidTest- β WriteKey validation (nonce, timestamp, signature, replay)
- β MAC tamper detection
- β Rate limiting enforcement
- β Origin verification (stack trace, ClassLoader, APK signature)
- β Asymmetric signature verification
- β Nonce store integrity (MAC protection)
- β Instance collision prevention (same propertyName across delegates)
| Operation | Time | Notes |
|---|---|---|
getValue() |
~0.5ms | AES-GCM decrypt + MAC verify |
authorizedWrite() |
~2-5ms | WriteKey validation + encryption |
| WriteKey validation | ~1-3ms | HMAC or ECDSA verify + nonce check |
| Nonce store MAC | ~0.5ms | HMAC over canonical nonce list |
| Risk detection | ~50-200ms | Full scan (root + hook + emulator) |
| Memory wipe | ~0.1ms | Reflection-based backing array zero |
β
Memory inspection: Variables encrypted at rest
β
Direct assignment: setValue() rejected, triggers alert
β
Replay attacks: Nonce tracking prevents reuse
β
Tampering: MAC + checksum detect modifications
β
Key forgery: HMAC/ECDSA signatures prevent unauthorized write keys
β
Context escalation: Signatures bound to specific users, properties, and scopes
β
Time manipulation: TTL + clock skew enforcement
β
Unauthorized origins: Stack trace + ClassLoader + APK signature verification
β
Rate abuse: Per-variable write throttling
β
Cross-instance state injection: Per-instance salt prevents key reuse
β
Nonce store tampering: Integrity MAC detects modifications
β
Root/Magisk/KernelSU: Multi-signal detection with SELinux and Zygisk checks
β
Frida/Xposed/Substrate: Thread scanning, port probing, fd inspection
β
Emulator & debugger: Build fingerprint and TracerPid detection
β
Plaintext exposure: SecureMemory wipe + periodic background sweeps
β
MITM attacks: CertificatePinning with backup pin support
| Threat | Mitigation | Residual Risk |
|---|---|---|
| Root access | RiskDetector detects Magisk, KernelSU, SU binaries, mount namespace cloaking, SELinux permissive mode, Zygisk/Shamiko DenyList artifacts, /proc access tampering, and native property reading (bypasses hooked getprop); triggers risk callback |
Kernel-level rootkits with fully custom cloaking remain theoretically possible |
| Frida/Xposed | RiskDetector scans /proc/maps, checks loaded framework classes, probes default + extended port range (27000β27100) with D-Bus protocol fingerprinting, scans thread names (/proc/self/task/*/comm), checks /proc/self/fd for injected artifacts, and validates process name integrity |
Attackers who rename all artifacts AND patch thread names simultaneously (extremely difficult) |
| Physical device access | AndroidKeyStore + Tink encryption; risk callback allows app-level response | Hardware key extraction remains possible on some devices |
| Stack trace spoofing | OriginVerifier adds ClassLoader validation + APK signature pinning + call chain depth validation + expected method name verification |
Kernel-level call stack manipulation (extremely rare) |
| Plaintext exposure | SecureMemory wipes String backing arrays via reflection; withSecureScope auto-wipes on exit; GC hint after every wipe; PeriodicWiper background sweeps every 30s |
JIT may retain register copies briefly (< 30s exposure window) |
| Server compromise | CertificatePinning helper with backup pins for key rotation; ECDSA signatures require private key |
Compromised private key remains out of scope |
SecureVarConfig(
action = SecureVarAction.Alert("https://your-server.com/alert"),
secretProvider = mySecretProvider, // MAC/ENC secret source
writeKeyVerifier = myWriteKeyVerifier, // Custom WriteKey validator
originVerifier = OriginVerifier.Builder(context) // Multi-signal origin verification
.allowPackage("com.yourapp")
.pinSignature("SHA-256:AB:CD:...")
.expectCallDepth(5..30)
.expectCallerMethod("authorizedWrite")
.build(),
allowedCallerPackages = listOf("com.yourapp"), // Fallback stack trace prefixes
context = applicationContext, // For runtime risk detection
onRiskDetected = { report -> // Risk callback
Log.w("SecureVar", "Risk: ${report.details}")
}
)sealed class SecureVarAction {
data class Alert(val url: String) : SecureVarAction() // Send alert to backend
object Logout : SecureVarAction() // Force user logout
object Crash : SecureVarAction() // Crash app immediately
}interface SecretProvider {
fun getMacSecret(): String // MAC key for HMAC operations
fun getEncSecret(propertyName: String): String // Base secret for AES key derivation
}securevar/
βββ SecureVarDelegate.kt # Core delegate with sealing/unsealing
βββ SecureVarManager.kt # Central config & lifecycle manager
βββ SecureVarWriter.kt # Type-safe write helpers
βββ WriteKeyValidator.kt # WriteKey nonce/signature validation
βββ integrity/
β βββ PlayIntegrityManager.kt # Google Play Integrity API
β βββ WriteKeyIntegrityBundler.kt
βββ memory/
β βββ SecureCharArrayDelegate.kt # Char array wrapper
β βββ SecureMemory.kt # String wiping + PeriodicWiper
βββ risk/
β βββ RiskDetector.kt # Root/hook/emulator/debugger detection
βββ security/
βββ CertificatePinning.kt # MITM protection with backup pins
βββ EncryptedDataStore.kt # Tink AEAD encrypted storage
βββ OriginVerifier.kt # Multi-signal origin validation
- Security Architecture - Detailed threat model and cryptographic design
This project is licensed under the Apache License 2.0 - see LICENSE file for details.
- Google Tink for AEAD encryption
- AndroidX DataStore for modern encrypted storage
- Google Play Integrity API for device attestation
- Kotlin coroutines for async operations
Built with β€οΈ for Android security