- ✅ EncryptedSharedPreferences with
AES256_GCMencryption - ✅ Android Keystore via
MasterKey.Builder - ✅ Keys stored in app's private storage (
data/data/com.yourown.ai) - ✅ No backup allowed (
android:allowBackup="false") - ✅ No auto-backup (
android:fullBackupContent="false")
Implementation: ApiKeyRepository.kt
private val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val encryptedPrefs = EncryptedSharedPreferences.create(
context,
PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)- ✅ HTTPS only (no cleartext traffic:
android:usesCleartextTraffic="false") - ✅ TLS/SSL for all API requests
- ✅ Certificate pinning ready via
network_security_config.xml - ✅ Authorization via
Bearertoken in header - ✅ No API keys in URL parameters (always in headers)
Implementation: DeepseekClient.kt
val request = Request.Builder()
.url("https://api.deepseek.com/chat/completions")
.header("Authorization", "Bearer $apiKey")
.post(body)
.build()- ✅
network_security_config.xmlenforces HTTPS-only - ✅ System certificate trust anchors
- ✅ Specific domain allowlist:
api.deepseek.comapi.openai.comapi.anthropic.comapi.x.aihuggingface.co
- ✅ Debug only:
HttpLoggingInterceptorenabled only in debug builds - ✅ Header redaction:
AuthorizationandAPI-Keyheaders are redacted - ✅ ProGuard: All logs stripped in release builds
Implementation: NetworkModule.kt
if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
redactHeader("Authorization")
redactHeader("API-Key")
}
builder.addInterceptor(loggingInterceptor)
}- ✅ ProGuard/R8 enabled with
isMinifyEnabled = true - ✅ Logs removed:
android.util.Logstripped - ✅ API key repository classes protected
- ✅ Network classes obfuscated
- ✅ Encryption classes preserved
ProGuard rules: proguard-rules.pro
# Remove all logging in release
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
# Security: Keep encryption classes
-keep class androidx.security.crypto.** { *; }
-keep class com.google.crypto.tink.** { *; }
- ✅ Only last 4 characters shown:
****1234 - ✅ No full key ever displayed in UI
- ✅ Masked input fields (TODO: add
passwordVisualTransformation) - ✅ No screenshots allowed for sensitive screens (TODO:
FLAG_SECURE)
Implementation: ApiKeyRepository.kt
fun getDisplayKey(provider: AIProvider): String? {
val key = _apiKeys.value[provider]
return if (key != null && key.length > 4) {
"****${key.takeLast(4)}"
} else {
null
}
}- Detect rooted devices
- Warn users about security risks
- Optional: disable API key storage on rooted devices
Update network_security_config.xml to pin certificates for critical APIs.
- Optional biometric lock for API key access
- Use
BiometricPromptAPI
| Requirement | Status | Implementation |
|---|---|---|
| Encrypt sensitive data at rest | ✅ | EncryptedSharedPreferences + Android Keystore |
| Use HTTPS for network communication | ✅ | usesCleartextTraffic="false" + Network Security Config |
| No hardcoded secrets in code | ✅ | User-provided API keys only |
| No logs with sensitive data in release | ✅ | ProGuard strips all logs |
| Obfuscate release builds | ✅ | ProGuard/R8 enabled |
| No backup of sensitive data | ✅ | allowBackup="false" |
| Secure key storage | ✅ | Android Keystore via MasterKey |
| Authorization via headers | ✅ | Bearer token in Authorization header |
| No cleartext traffic | ✅ | Network Security Config |