1. Code Obfuscation and Hardening
1.1 Enable Code Obfuscation Modify the app/build.gradle.kts file:
android { buildTypes { release { isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile(\ proguard- android- optimize.txt\), \proguard- rules.pro\ ) } debug { isMinifyEnabled = false } } }
Create or modify the app/proguard-rules.pro file:
# Basic configuration -keepattributes SourceFile,LineNumberTable -renamesourcefileattribute SourceFile -keepattributes *Annotation* -keepattributes Signature -keepattributes Exceptions # Kotlin configuration -keep class kotlin.Metadata { *; } -dontwarn kotlin.** -keepclassmembers class ** { <fields>; } # Kotlin Coroutines -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} -dontwarn kotlinx.coroutines.** # Jetpack Compose -keep class androidx.compose.** { *; } -dontwarn androidx.compose.** # Retrofit & OkHttp -keepattributes Signature, InnerClasses, EnclosingMethod -keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations -keepclassmembers,allowshrinking,allowobfuscation interface * { @retrofit2.http.* <methods>; } -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn javax.annotation.** -dontwarn kotlin.Unit -dontwarn retrofit2.KotlinExtensions -dontwarn retrofit2.KotlinExtensions$* # OkHttp -dontwarn okhttp3.** -dontwarn okio.** -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase # Gson -keepattributes Signature -keepattributes *Annotation* -dontwarn sun.misc.** -keep class com.google.gson.** { *; } -keep class * implements com.google.gson.TypeAdapter -keep class * implements com.google.gson.TypeAdapterFactory -keep class * implements com.google.gson.JsonSerializer -keep class * implements com.google.gson.JsonDeserializer # Keep data model classes -keep class com.awen.running.network.** { *; } -keep class com.awen.running.data.** { *; } # AMap SDK -keep class com.amap.api.** { *; } -keep class com.autonavi.** { *; } -dontwarn com.amap.api.** -dontwarn com.autonavi.** # Alibaba Cloud NUI SDK -keep class com.alibaba.** { *; } -dontwarn com.alibaba.** # FastJSON -keep class com.alibaba.fastjson.** { *; } -dontwarn com.alibaba.fastjson.** # MPAndroidChart -keep class com.github.mikephil.charting.** { *; } -dontwarn com.github.mikephil.charting.** # Bluetooth-related -keep class android.bluetooth.** { *; } -keep class com.awen.running.bluetooth.** { *; } # Strip sensitive info -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); public static *** i(...); } # Keep Serializable classes -keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Keep Parcelable classes -keep class * implements android.os.Parcelable { public static final android.os.Parcelable *; } # Keep enums -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }
2. Network Communication Security Modify the app/src/main/java/com/awen/running/network/RetrofitClient.kt file:
package com.awen.running.networkimport android.content.Contextimport android.content.SharedPreferencesimport com.awen.running.BuildConfigimport com.awen.running.security.EncryptedStorageimport okhttp3.CertificatePinnerimport okhttp3.OkHttpClientimport okhttp3.logging.HttpLoggingInterceptorimport retrofit2.Retrofitimport retrofit2.converter.gson.GsonConverterFactoryimport java.util.concurrent.TimeUnitobject RetrofitClient { private const val BASE_URL = \https: private var authToken: String? = null private var userId: Int ? = null private var applicationContext: Context? = null fun init (context: Context ) { applicationContext = context.applicationContext authToken = EncryptedStorage.getToken(context) userId = EncryptedStorage.getUserId(context) } private val certificatePinner = CertificatePinner.Builder() .add(\api.awen.me\, \sha256/v9InP1GnrRxH80k4Mrccvr2JwZEsrJh+LVyOGzcZmk4=\) .add(\api.awen.me\, \sha256/mTGuGxOfs9PK15aUeNsmQQQ6VL8f0bCvdvEEqkjC6M8=\) .build() private val loggingInterceptor = HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } } private val client: OkHttpClient by lazy { OkHttpClient.Builder() .connectTimeout(30 , TimeUnit.SECONDS) .readTimeout(30 , TimeUnit.SECONDS) .writeTimeout(30 , TimeUnit.SECONDS) .certificatePinner(certificatePinner) .addInterceptor(loggingInterceptor) .addInterceptor { chain -> val original = chain.request() val requestBuilder = original.newBuilder() .header(\Content-Type\, \application/json\) authToken?.let { requestBuilder.header(\Authorization\, \Bearer \) } chain.proceed(requestBuilder.build()) } .build() } val apiService: ApiService by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiService::class .java) } fun setToken (token: String , userId: Int ? = null ) { authToken = token this .userId = userId applicationContext?.let { context -> EncryptedStorage.saveToken(context, token) userId?.let { EncryptedStorage.saveUserId(context, it) } } } fun getToken () : String? = authToken fun getUserId () : Int ? = userId fun hasToken () : Boolean = !authToken.isNullOrEmpty() fun clearToken () { authToken = null userId = null applicationContext?.let { context -> EncryptedStorage.clearToken(context) } } }
2.2 Network Security Configuration Create the app/src/main/res/xml/network_security_config.xml file:
<?xml version=\1.0\ encoding=\utf-8\?> <network-security-config > <base-config cleartextTrafficPermitted =\false\ > <trust-anchors > <certificates src =\system\ /> </trust-anchors > </base-config > <domain-config cleartextTrafficPermitted =\false\ > <domain includeSubdomains =\true\ > api.awen.me</domain > </domain-config > </network-security-config >
Reference it in app/src/main/AndroidManifest.xml:
<application android:networkSecurityConfig =\@xml/network_security_config\ ... >
3. Data Storage Security 3.1 Add the Encrypted Storage Dependency Modify the app/build.gradle.kts file:
dependencies { implementation(\androidx.security:security-crypto:1.1 .0 -alpha06\) }
3.2 Create an Encrypted Storage Utility Class Create the app/src/main/java/com/awen/running/security/EncryptedStorage.kt file:
package com.awen.running.securityimport android.content.Contextimport android.content.SharedPreferencesimport androidx.security.crypto.EncryptedSharedPreferencesimport androidx.security.crypto.MasterKeyimport java.security.SecureRandomimport javax.crypto.Cipherimport javax.crypto.KeyGeneratorimport javax.crypto.SecretKeyimport javax.crypto.spec.GCMParameterSpecimport javax.crypto.spec.SecretKeySpecimport android.util.Base64object EncryptedStorage { private const val ENCRYPTED_PREFS_NAME = \encrypted_prefs\ private const val ANDROID_KEYSTORE = \AndroidKeyStore\ private const val AES_MODE = \AES/GCM/NoPadding\ private fun getEncryptedPrefs (context: Context ) : SharedPreferences { val masterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() return EncryptedSharedPreferences.create( context, ENCRYPTED_PREFS_NAME, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) } private const val KEY_JWT_TOKEN = \jwt_token\ private const val KEY_USER_ID = \user_id\ fun saveToken (context: Context , token: String ) { getEncryptedPrefs(context).edit() .putString(KEY_JWT_TOKEN, token) .apply() } fun getToken (context: Context ) : String? { return getEncryptedPrefs(context).getString(KEY_JWT_TOKEN, null ) } fun saveUserId (context: Context , userId: Int ) { getEncryptedPrefs(context).edit() .putInt(KEY_USER_ID, userId) .apply() } fun getUserId (context: Context ) : Int ? { return getEncryptedPrefs(context).getInt(KEY_USER_ID, -1 ).takeIf { it != -1 } } fun clearToken (context: Context ) { getEncryptedPrefs(context).edit() .remove(KEY_JWT_TOKEN) .remove(KEY_USER_ID) .apply() } fun hasValidToken (context: Context ) : Boolean { val token = getToken(context) return !token.isNullOrEmpty() } private const val KEY_RESTING_HEART_RATE = \resting_heart_rate\ private const val KEY_HEART_RATE_ZONE_SETTINGS = \heart_rate_zone_settings\ fun saveRestingHeartRate (context: Context , restingRate: Int ) { getEncryptedPrefs(context).edit() .putInt(KEY_RESTING_HEART_RATE, restingRate) .apply() } fun getRestingHeartRate (context: Context ) : Int ? { val rate = getEncryptedPrefs(context).getInt(KEY_RESTING_HEART_RATE, -1 ) return if (rate != -1 ) rate else null } fun saveHeartRateZoneSettings (context: Context , settings: String ) { getEncryptedPrefs(context).edit() .putString(KEY_HEART_RATE_ZONE_SETTINGS, settings) .apply() } fun getHeartRateZoneSettings (context: Context ) : String? { return getEncryptedPrefs(context).getString(KEY_HEART_RATE_ZONE_SETTINGS, null ) } fun clearAll (context: Context ) { getEncryptedPrefs(context).edit() .clear() .apply() } }
4. App Signing and Release 4.1 Generate a Signing Key Run the following command on the command line:
keytool -genkey -v -keystore running-release.keystore \ -alias running-key \ -keyalg RSA \ -keysize 2048 \ -validity 10000
Modify the app/build.gradle.kts file:
android { signingConfigs { create(\release\) { storeFile = file(rootDir.absolutePath + \/running-release.keystore\) storePassword = System.getenv(\KEYSTORE_PASSWORD\) ?: System.getProperty(\KEYSTORE_PASSWORD\) keyAlias = \running-key\ keyPassword = System.getenv(\KEY_PASSWORD\) ?: System.getProperty(\KEY_PASSWORD\) } } buildTypes { release { signingConfig = signingConfigs.getByName(\release\) isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile(\proguard-android-optimize.txt\), \proguard-rules.pro\ ) } debug { isMinifyEnabled = false } } }
4.3 Secure Build Commands Production environment :
KEYSTORE_PASSWORD=your_password KEY_PASSWORD=your_password ./gradlew assembleRelease
PowerShell environment :
=\your_password\; =\your_password\; ./gradlew assembleRelease
Local development environment :
./gradlew assembleRelease -DKEYSTORE_PASSWORD=your_password -DKEY_PASSWORD=your_password
5. Runtime Security Detection 5.1 Create a Security Detection Utility Class Create the app/src/main/java/com/awen/running/security/SecurityUtils.kt file:
package com.awen.running.securityimport android.content.Contextimport android.os.Buildimport android.provider.Settingsimport java.io.Fileobject SecurityUtils { fun isDeviceRooted () : Boolean { return checkRootMethod1() || checkRootMethod2() || checkRootMethod3() } private fun checkRootMethod1 () : Boolean { val paths = arrayOf( \/system/app/Superuser.apk\, \/sbin/su\, \/system/bin/su\, \/system/xbin/su\, \/data /local/xbin/su\, \/data /local/bin/su\, \/system/sd/xbin/su\, \/system/bin/failsafe/su\, \/data /local/su\, \/su/bin/su\ ) for (path in paths) { if (File(path).exists()) { return true } } return false } private fun checkRootMethod2 () : Boolean { var process: Process? = null return try { process = Runtime.getRuntime().exec(arrayOf(\/system/xbin/which\, \su\)) val bufferedReader = process.inputStream.bufferedReader() bufferedReader.readLine() != null } catch (e: Exception) { false } finally { process?.destroy() } } private fun checkRootMethod3 () : Boolean { val buildTags = Build.TAGS return buildTags != null && buildTags.contains(\test-keys\) } fun isEmulator () : Boolean { return (Build.FINGERPRINT.startsWith(\generic\) || Build.FINGERPRINT.startsWith(\unknown\) || Build.MODEL.contains(\google_sdk\) || Build.MODEL.contains(\Emulator\) || Build.MODEL.contains(\Android SDK builtfor x86\) || Build.MANUFACTURER.contains(\Genymotion\) || (Build.BRAND.startsWith(\generic\) && Build.DEVICE.startsWith(\generic\)) || \google_sdk\ == Build.PRODUCT || Build.HARDWARE.contains(\goldfish\) || Build.HARDWARE.contains(\ranchu\)) } fun isDebuggerConnected () : Boolean { return android.os.Debug.isDebuggerConnected() } fun isUsbDebuggingEnabled (context: Context ) : Boolean { return Settings.Global.getInt( context.contentResolver, Settings.Global.ADB_ENABLED, 0 ) > 0 } fun performSecurityCheck (context: Context ) : SecurityCheckResult { val risks = mutableListOf<String>() if (isDeviceRooted()) { risks.add(\Device is rooted, security risk present\) } if (isEmulator()) { risks.add(\Emulator environment detected\) } if (isDebuggerConnected()) { risks.add(\Debugger connection detected\) } if (isUsbDebuggingEnabled(context)) { risks.add(\USB debugging is enabled\) } return SecurityCheckResult( isSafe = risks.isEmpty(), risks = risks ) } data class SecurityCheckResult ( val isSafe: Boolean , val risks: List<String> ) }
5.2 Use Security Detection in the App Add to MainActivity.kt or Application.kt:
override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) if (!BuildConfig.DEBUG) { val result = SecurityUtils.performSecurityCheck(this ) if (!result.isSafe) { AlertDialog.Builder(this ) .setTitle(\Security Warning\) .setMessage(result.risks.joinToString(\\\n\)) .setPositiveButton(\Continue\) { _, _ -> } .setNegativeButton(\Exit\) { _, _ -> finish() } .show() } } }
5.3 Anti-Screenshot Protection Add anti-screenshot to sensitive pages:
override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) }
6. App Hardening The following hardening platforms are recommended:
6.2 Hardening Process # 1. Compile the Release build ./gradlew assembleRelease # 2. Upload to the hardening platform # - Select hardening options # - Download the hardened APK # - Re-sign it (hardening breaks the signature) # 3. Test the hardened APK
7. Complete Implementation Checklist 7.1 Files to Modify
app/build.gradle.kts - configure obfuscation, signing, dependencies
app/proguard-rules.pro - ProGuard rules
app/src/main/java/com/awen/running/network/RetrofitClient.kt - network security configuration
app/src/main/res/xml/network_security_config.xml - network security configuration
app/src/main/java/com/awen/running/security/EncryptedStorage.kt - encrypted storage
app/src/main/java/com/awen/running/security/SecurityUtils.kt - security detection
app/src/main/AndroidManifest.xml - reference the network security configuration
7.2 Commands to Run
Generate the signing key: keytool -genkey -v -keystore running-release.keystore -alias running-key -keyalg RSA -keysize 2048 -validity 10000
Build Release: KEYSTORE_PASSWORD=your_password KEY_PASSWORD=your_password ./gradlew assembleRelease
7.3 Verification Steps
Check whether obfuscation took effect: decompile the APK and see whether the code is obfuscated
Verify SSL certificate pinning: try using Charles to capture HTTPS requests
Confirm data encryption: check the contents of the SharedPreferences file
Verify the signature: check the APK signature info
8. Security Level Assessment
Security measure
Status
Score
Code obfuscation
Enabled
SSL certificate pinning
Configured
Sensitive data encryption
Enabled
Conditional log output
Enabled
Security detection
Integrated
App hardening
Recommended
Overall security level : Enterprise-grade (suitable for commercial apps)
9. Summary By implementing the above security measures, your Android app will have:
Protection at the code level (obfuscation and hardening)
Security of network communication (SSL certificate pinning)
Security of data storage (encrypted storage)
Security of the runtime environment (security detection)
Security of the release build (signature protection)
These measures will greatly improve the app’s security and protect user data and corporate interests. Remember, security is an ongoing process; security measures need to be regularly assessed and updated to cope with new threats.
Document created : February 2026Version : 1.0
评论
0 条评论