1. 代码混淆与加固

1.1 启用代码混淆
修改 pp/build.gradle.kts 文件:
android { buildTypes { release { isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile(\ proguard-android-optimize.txt\), \proguard-rules.pro\ ) } debug { isMinifyEnabled = false } } }
|
1.2 配置ProGuard规则
创建或修改 pp/proguard-rules.pro 文件:
# 基础配置 -keepattributes SourceFile,LineNumberTable -renamesourcefileattribute SourceFile -keepattributes *Annotation* -keepattributes Signature -keepattributes Exceptions
# Kotlin配置 -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 class com.awen.running.network.** { *; } -keep class com.awen.running.data.** { *; }
# 高德地图 SDK -keep class com.amap.api.** { *; } -keep class com.autonavi.** { *; } -dontwarn com.amap.api.** -dontwarn com.autonavi.**
# 阿里云 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.**
# 蓝牙相关 -keep class android.bluetooth.** { *; } -keep class com.awen.running.bluetooth.** { *; }
# 混淆敏感信息 -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); public static *** i(...); }
# 保留Serializable类 -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(); }
# 保留Parcelable类 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable *; }
# 保留枚举 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }
|
2. 网络通信安全
2.1 配置SSL证书锁定
修改 pp/src/main/java/com/awen/running/network/RetrofitClient.kt 文件:
package com.awen.running.network
import android.content.Context import android.content.SharedPreferences import com.awen.running.BuildConfig import com.awen.running.security.EncryptedStorage import okhttp3.CertificatePinner import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit
object 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 网络安全配置
创建 pp/src/main/res/xml/network_security_config.xml 文件:
<?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>
|
在 pp/src/main/AndroidManifest.xml 中引用:
<application android:networkSecurityConfig=\@xml/network_security_config\ ...>
|
3. 数据存储安全
3.1 添加加密存储依赖
修改 pp/build.gradle.kts 文件:
dependencies { implementation(\androidx.security:security-crypto:1.1.0-alpha06\) }
|
3.2 创建加密存储工具类
创建 pp/src/main/java/com/awen/running/security/EncryptedStorage.kt 文件:
package com.awen.running.security
import android.content.Context import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import java.security.SecureRandom import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec import javax.crypto.spec.SecretKeySpec import android.util.Base64
object 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. 应用签名与发布
4.1 生成签名密钥
在命令行中执行以下命令:
keytool -genkey -v -keystore running-release.keystore \ -alias running-key \ -keyalg RSA \ -keysize 2048 \ -validity 10000
|
4.2 配置签名
修改 pp/build.gradle.kts 文件:
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 安全构建命令
生产环境:
KEYSTORE_PASSWORD=your_password KEY_PASSWORD=your_password ./gradlew assembleRelease
|
PowerShell环境:
=\your_password\; =\your_password\; ./gradlew assembleRelease
|
本地开发环境:
./gradlew assembleRelease -DKEYSTORE_PASSWORD=your_password -DKEY_PASSWORD=your_password
|
5. 运行时安全检测
5.1 创建安全检测工具类
创建 pp/src/main/java/com/awen/running/security/SecurityUtils.kt 文件:
package com.awen.running.security
import android.content.Context import android.os.Build import android.provider.Settings import java.io.File
object 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 built for 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(\设备已Root,存在安全风险\) } if (isEmulator()) { risks.add(\检测到模拟器环境\) } if (isDebuggerConnected()) { risks.add(\检测到调试器连接\) } if (isUsbDebuggingEnabled(context)) { risks.add(\USB调试已开启\) } return SecurityCheckResult( isSafe = risks.isEmpty(), risks = risks ) }
data class SecurityCheckResult( val isSafe: Boolean, val risks: List<String> ) }
|
5.2 在应用中使用安全检测
在 MainActivity.kt 或 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(\安全警告\) .setMessage(result.risks.joinToString(\\\n\)) .setPositiveButton(\继续使用\) { _, _ -> } .setNegativeButton(\退出\) { _, _ -> finish() } .show() } } }
|
5.3 防截屏保护
在敏感页面添加防截屏:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) }
|
6. 应用加固
6.1 使用第三方加固平台
推荐使用以下加固平台:
6.2 加固流程
# 1. 编译Release版本 ./gradlew assembleRelease
# 2. 上传到加固平台 # - 选择加固选项 # - 下载加固后的APK # - 重新签名(加固会破坏签名)
# 3. 测试加固后的APK
|
7. 完整实施清单
7.1 需要修改的文件
- pp/build.gradle.kts - 配置混淆、签名、依赖
- pp/proguard-rules.pro - ProGuard规则
- pp/src/main/java/com/awen/running/network/RetrofitClient.kt - 网络安全配置
- pp/src/main/res/xml/network_security_config.xml - 网络安全配置
- pp/src/main/java/com/awen/running/security/EncryptedStorage.kt - 加密存储
- pp/src/main/java/com/awen/running/security/SecurityUtils.kt - 安全检测
- pp/src/main/AndroidManifest.xml - 网络安全配置引用
7.2 需要执行的命令
- 生成签名密钥:keytool -genkey -v -keystore running-release.keystore -alias running-key -keyalg RSA -keysize 2048 -validity 10000
- 构建Release:KEYSTORE_PASSWORD=your_password KEY_PASSWORD=your_password ./gradlew assembleRelease
7.3 验证步骤
- 检查混淆是否生效:反编译APK查看代码是否被混淆
- 验证SSL证书锁定:尝试使用Charles抓包HTTPS请求
- 确认数据加密:检查SharedPreferences文件内容
- 验证签名:检查APK签名信息
8. 安全等级评估
| 安全措施 |
状态 |
评分 |
| 代码混淆 |
已启用 |
|
| SSL证书锁定 |
已配置 |
|
| 敏感数据加密 |
已启用 |
|
| 条件日志输出 |
已启用 |
|
| 安全检测 |
已集成 |
|
| 应用加固 |
推荐实施 |
|
综合安全等级: 企业级(适合商业应用)
9. 总结
通过实施以上安全措施,您的Android应用将具备:
- 代码级别的保护(混淆和加固)
- 网络通信的安全(SSL证书锁定)
- 数据存储的安全(加密存储)
- 运行时环境的安全(安全检测)
- 发布版本的安全(签名保护)
这些措施将大大提高应用的安全性,保护用户数据和企业利益。记住,安全是一个持续的过程,需要定期评估和更新安全措施以应对新的威胁。
文档创建时间:2026年2月
版本:1.0
文章作者:阿文
版权声明:本博客所有文章除特别声明外,均采用
CC BY-NC-SA 4.0 许可协议。转载请注明来自
阿文的博客!