You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin应用重要Token防反编译隐藏方案咨询(Target SDK 33)

Kotlin Android应用中Token的安全隐藏方案(目标SDK 33)

针对你遇到的Token被反编译轻易读取的问题,以下是几个切实可行的方案,比单纯minify更有效:

1. 彻底避免硬编码,动态按需获取

永远不要把Token直接写在代码、资源文件或配置文件里。最佳方式是在应用启动或需要使用Token时,从后端接口临时获取,使用完成后立即从内存中清除(比如将持有Token的变量置为null)。

示例代码:

suspend fun fetchToken(): String? {
    return try {
        val response = apiService.getToken()
        val token = response.token
        // 使用token完成操作后及时置空
        token
    } catch (e: Exception) {
        null
    }
}

2. 使用Android Keystore加密存储

Android Keystore是系统级安全存储,密钥由系统管理无法导出,适合加密敏感数据如Token。可以生成对称密钥或非对称密钥对,加密Token后存储在SharedPreferences或数据库中,使用时解密。

示例代码(AES加密存储Token):

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

class KeystoreHelper {
    private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    private val alias = "my_token_key"

    init {
        generateKeyIfNeeded()
    }

    private fun generateKeyIfNeeded() {
        if (!keyStore.containsAlias(alias)) {
            val keyGenerator = KeyGenerator.getInstance(
                KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
            )
            val spec = KeyGenParameterSpec.Builder(
                alias,
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
            )
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .setUserAuthenticationRequired(false) // 根据需求设置是否需要用户验证
                .build()
            keyGenerator.init(spec)
            keyGenerator.generateKey()
        }
    }

    fun encryptToken(token: String): Pair<ByteArray, ByteArray> {
        val secretKey = keyStore.getKey(alias, null) as SecretKey
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.ENCRYPT_MODE, secretKey)
        val encryptedBytes = cipher.doFinal(token.toByteArray())
        return encryptedBytes to cipher.iv
    }

    fun decryptToken(encryptedBytes: ByteArray, iv: ByteArray): String {
        val secretKey = keyStore.getKey(alias, null) as SecretKey
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val spec = GCMParameterSpec(128, iv)
        cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
        val decryptedBytes = cipher.doFinal(encryptedBytes)
        return String(decryptedBytes)
    }
}

使用时将加密后的字节数组和IV存储到SharedPreferences,读取时解密即可。

3. 字符串拆分与分散存储

将Token拆分成多个片段,分散存储在不同位置:比如一部分存strings.xml(需开启资源混淆),一部分存SharedPreferences,一部分存native层静态变量,使用时再拼接。

示例:

// 假设Token拆分为三段
val part1 = getString(R.string.token_part1) // 存strings.xml
val part2 = sharedPreferences.getString("token_part2", "") // 存SharedPreferences
val part3 = getNativeTokenPart() // 从native层获取
val fullToken = part1 + part2 + part3

4. 借助Native层加密与存储

将Token的加密、解密逻辑放在C/C++层,通过JNI调用获取。Native层代码反编译难度远高于Kotlin,即使被反编译,也需要逆向分析汇编代码,大幅提高攻击成本。

Kotlin端JNI调用示例:

external fun getDecryptedToken(): String

// 使用
val token = getDecryptedToken()

Native层(C++)示例:

#include <jni.h>
#include <string>
#include "crypto_utils.h" // 自定义加密解密逻辑

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_yourapp_MainActivity_getDecryptedToken(JNIEnv* env, jobject thiz) {
    std::string encryptedToken = readEncryptedTokenFromFile(); // 从私有文件读取加密Token
    std::string decryptedToken = decrypt(encryptedToken); // 自定义解密逻辑
    return env->NewStringUTF(decryptedToken.c_str());
}

5. 加密SharedPreferences存储

不要使用默认未加密的SharedPreferences,自己实现AES加密后再存储Token,即使SharedPreferences文件被获取,也无法直接读取明文。

示例代码:

import android.content.Context
import android.content.SharedPreferences
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec

class EncryptedSharedPref(private val context: Context) {
    private val sharedPref = context.getSharedPreferences("encrypted_prefs", Context.MODE_PRIVATE)
    // 注意:密钥不要硬编码,建议从Android Keystore中获取
    private val secretKey = getKeyFromKeystore()

    fun saveToken(token: String) {
        val encryptedToken = encrypt(token)
        sharedPref.edit().putString("token", encryptedToken).apply()
    }

    fun getToken(): String? {
        val encryptedToken = sharedPref.getString("token", null) ?: return null
        return decrypt(encryptedToken)
    }

    private fun encrypt(data: String): String {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.ENCRYPT_MODE, secretKey)
        val encryptedBytes = cipher.doFinal(data.toByteArray())
        return android.util.Base64.encodeToString(encryptedBytes, android.util.Base64.DEFAULT)
    }

    private fun decrypt(data: String): String {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val decodedBytes = android.util.Base64.decode(data, android.util.Base64.DEFAULT)
        // 需保存加密时的IV,此处省略IV存储逻辑,建议和加密数据一起存储
        cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
        val decryptedBytes = cipher.doFinal(decodedBytes)
        return String(decryptedBytes)
    }

    private fun getKeyFromKeystore(): SecretKey {
        // 从Android Keystore获取密钥的逻辑,可复用KeystoreHelper中的实现
        return KeystoreHelper().getSecretKey()
    }
}

额外建议

  • 开启R8混淆的同时启用资源混淆,防止资源文件中的敏感片段被读取
  • Release包中禁用debuggable属性,限制调试权限,防止通过调试工具获取内存中的Token
  • 给Token设置较短有效期,即使泄露,影响范围也有限

内容的提问来源于stack exchange,提问作者Sajjad Akbari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:23:10