移动端安全攻防

5 阅读12分钟

移动端安全攻防

对称加密

概述

对称加密使用相同的密钥进行加密和解密。常见算法包括 AES、DES、3DES 等。AES (Advanced Encryption Standard) 是目前最广泛使用的对称加密算法。

特点

  • 加密速度快
  • 适合大量数据加密
  • 密钥分发困难
  • 密钥泄露风险高

AES-256 加密实现(Kotlin)

import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import javax.crypto.spec.IvParameterSpec
import android.util.Base64
import java.security.SecureRandom

object AESCrypto {
    private const val ALGORITHM = "AES"
    private const val TRANSFORMATION = "AES/CBC/PKCS5Padding"

    /**
     * 生成AES密钥
     */
    fun generateKey(): ByteArray {
        val key = ByteArray(32) // 256-bit key
        SecureRandom().nextBytes(key)
        return key
    }

    /**
     * AES加密
     */
    fun encrypt(data: String, key: ByteArray): Pair<String, String> {
        val cipher = Cipher.getInstance(TRANSFORMATION)
        val secretKey = SecretKeySpec(key, ALGORITHM)

        // 生成随机IV
        val iv = ByteArray(16)
        SecureRandom().nextBytes(iv)
        val ivSpec = IvParameterSpec(iv)

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)
        val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8))

        return Pair(
            Base64.encodeToString(encrypted, Base64.NO_WRAP),
            Base64.encodeToString(iv, Base64.NO_WRAP)
        )
    }

    /**
     * AES解密
     */
    fun decrypt(encryptedData: String, key: ByteArray, ivString: String): String {
        val cipher = Cipher.getInstance(TRANSFORMATION)
        val secretKey = SecretKeySpec(key, ALGORITHM)
        val iv = Base64.decode(ivString, Base64.NO_WRAP)
        val ivSpec = IvParameterSpec(iv)

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
        val decrypted = cipher.doFinal(Base64.decode(encryptedData, Base64.NO_WRAP))

        return String(decrypted, Charsets.UTF_8)
    }
}

// 使用示例
fun main() {
    val key = AESCrypto.generateKey()
    val plaintext = "敏感数据"

    val (encrypted, iv) = AESCrypto.encrypt(plaintext, key)
    println("加密结果: $encrypted")

    val decrypted = AESCrypto.decrypt(encrypted, key, iv)
    println("解密结果: $decrypted")
}

高性能场景:Rust实现

在性能关键场景(如大文件加密、实时通信),可以使用Rust通过FFI提供给移动端:

use aes::Aes256;
use block_modes::{BlockMode, Cbc};
use block_modes::block_padding::Pkcs7;
use rand::Rng;

type Aes256Cbc = Cbc<Aes256, Pkcs7>;

pub struct AESCrypto;

impl AESCrypto {
    const KEY_SIZE: usize = 32; // 256-bit
    const IV_SIZE: usize = 16;  // 128-bit

    /// 生成随机密钥
    pub fn generate_key() -> [u8; Self::KEY_SIZE] {
        let mut key = [0u8; Self::KEY_SIZE];
        rand::thread_rng().fill(&mut key);
        key
    }

    /// AES加密
    pub fn encrypt(plaintext: &str, key: &[u8; Self::KEY_SIZE]) -> Result<(Vec<u8>, [u8; Self::IV_SIZE]), String> {
        let mut iv = [0u8; Self::IV_SIZE];
        rand::thread_rng().fill(&mut iv);

        let cipher = Aes256Cbc::new_from_slices(key, &iv)
            .map_err(|e| format!("创建加密器失败: {}", e))?;

        let ciphertext = cipher.encrypt_vec(plaintext.as_bytes());
        Ok((ciphertext, iv))
    }

    /// AES解密
    pub fn decrypt(ciphertext: &[u8], key: &[u8; Self::KEY_SIZE], iv: &[u8; Self::IV_SIZE]) -> Result<String, String> {
        let cipher = Aes256Cbc::new_from_slices(key, iv)
            .map_err(|e| format!("创建解密器失败: {}", e))?;

        let decrypted = cipher.decrypt_vec(ciphertext)
            .map_err(|e| format!("解密失败: {}", e))?;

        String::from_utf8(decrypted)
            .map_err(|e| format!("UTF-8转换失败: {}", e))
    }
}

// FFI导出供Kotlin/Swift调用
#[no_mangle]
pub extern "C" fn aes_encrypt_fast(/* ... */) -> *mut u8 {
    // 供移动端调用的C接口
}

非对称加密

概述

非对称加密使用公钥加密、私钥解密(或私钥签名、公钥验证)。常见算法包括 RSA、ECC、Ed25519 等。

特点

  • 密钥分发安全
  • 支持数字签名
  • 无需共享私钥
  • 加密速度慢
  • 只能加密少量数据

RSA 加密实现(Kotlin)

import java.security.*
import javax.crypto.Cipher
import android.util.Base64

object RSACrypto {
    private const val ALGORITHM = "RSA"
    private const val TRANSFORMATION = "RSA/ECB/PKCS1Padding"
    private const val KEY_SIZE = 2048

    /**
     * 生成RSA密钥对
     */
    fun generateKeyPair(): KeyPair {
        val keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM)
        keyPairGenerator.initialize(KEY_SIZE, SecureRandom())
        return keyPairGenerator.generateKeyPair()
    }

    /**
     * 公钥加密
     */
    fun encrypt(data: String, publicKey: PublicKey): String {
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.ENCRYPT_MODE, publicKey)
        val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
        return Base64.encodeToString(encrypted, Base64.NO_WRAP)
    }

    /**
     * 私钥解密
     */
    fun decrypt(encryptedData: String, privateKey: PrivateKey): String {
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.DECRYPT_MODE, privateKey)
        val decrypted = cipher.doFinal(Base64.decode(encryptedData, Base64.NO_WRAP))
        return String(decrypted, Charsets.UTF_8)
    }

    /**
     * 私钥签名
     */
    fun sign(data: String, privateKey: PrivateKey): String {
        val signature = Signature.getInstance("SHA256withRSA")
        signature.initSign(privateKey)
        signature.update(data.toByteArray(Charsets.UTF_8))
        return Base64.encodeToString(signature.sign(), Base64.NO_WRAP)
    }

    /**
     * 公钥验证签名
     */
    fun verify(data: String, signatureStr: String, publicKey: PublicKey): Boolean {
        val signature = Signature.getInstance("SHA256withRSA")
        signature.initVerify(publicKey)
        signature.update(data.toByteArray(Charsets.UTF_8))
        return signature.verify(Base64.decode(signatureStr, Base64.NO_WRAP))
    }
}

// 使用示例
fun main() {
    val keyPair = RSACrypto.generateKeyPair()
    val plaintext = "敏感数据"

    // 加密解密
    val encrypted = RSACrypto.encrypt(plaintext, keyPair.public)
    println("加密结果: $encrypted")

    val decrypted = RSACrypto.decrypt(encrypted, keyPair.private)
    println("解密结果: $decrypted")

    // 签名验证
    val signature = RSACrypto.sign(plaintext, keyPair.private)
    val isValid = RSACrypto.verify(plaintext, signature, keyPair.public)
    println("签名验证: $isValid")
}

Native层RSA实现(C++)

用于密钥保护和性能优化,防止Java层被Hook:

#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <jni.h>

class NativeRSA {
public:
    static constexpr int KEY_SIZE = 2048;

    // 生成密钥对
    static EVP_PKEY* generateKeyPair() {
        EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
        EVP_PKEY* pkey = nullptr;

        EVP_PKEY_keygen_init(ctx);
        EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KEY_SIZE);
        EVP_PKEY_keygen(ctx, &pkey);

        EVP_PKEY_CTX_free(ctx);
        return pkey;
    }

    // 公钥加密
    static bool encrypt(
        const std::string& plaintext,
        EVP_PKEY* publicKey,
        std::vector<unsigned char>& ciphertext
    ) {
        EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(publicKey, nullptr);
        if (!ctx) return false;

        if (EVP_PKEY_encrypt_init(ctx) <= 0) {
            EVP_PKEY_CTX_free(ctx);
            return false;
        }

        if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
            EVP_PKEY_CTX_free(ctx);
            return false;
        }

        size_t outlen;
        if (EVP_PKEY_encrypt(ctx, nullptr, &outlen,
                            reinterpret_cast<const unsigned char*>(plaintext.data()),
                            plaintext.size()) <= 0) {
            EVP_PKEY_CTX_free(ctx);
            return false;
        }

        ciphertext.resize(outlen);
        if (EVP_PKEY_encrypt(ctx, ciphertext.data(), &outlen,
                            reinterpret_cast<const unsigned char*>(plaintext.data()),
                            plaintext.size()) <= 0) {
            EVP_PKEY_CTX_free(ctx);
            return false;
        }

        EVP_PKEY_CTX_free(ctx);
        return true;
    }
};

// JNI导出
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_crypto_NativeRSA_encrypt(
    JNIEnv* env, jobject, jstring plaintext, jlong publicKeyPtr
) {
    // Native层加密实现,防止被Hook
}

接口加解密应用

混合加密方案

在实际的移动应用开发中,通常采用对称加密 + 非对称加密的混合方案:

  1. 客户端生成随机AES密钥
  2. 使用AES密钥加密请求数据(快速)
  3. 使用服务器RSA公钥加密AES密钥(安全传输密钥)
  4. 同时发送加密数据和加密后的密钥

Kotlin 混合加密实现

data class EncryptedRequest(
    val encryptedData: String,
    val encryptedKey: String,
    val iv: String,
    val timestamp: Long,
    val signature: String? = null
)

object HybridCrypto {

    /**
     * 加密API请求
     */
    fun encryptRequest(
        jsonData: String,
        serverPublicKey: PublicKey
    ): EncryptedRequest {
        // 1. 生成随机AES密钥
        val aesKey = AESCrypto.generateKey()

        // 2. 使用AES加密数据
        val (encryptedData, iv) = AESCrypto.encrypt(jsonData, aesKey)

        // 3. 使用RSA公钥加密AES密钥
        val encryptedKey = RSACrypto.encrypt(
            Base64.encodeToString(aesKey, Base64.NO_WRAP),
            serverPublicKey
        )

        // 4. 添加时间戳防止重放攻击
        val timestamp = System.currentTimeMillis()

        return EncryptedRequest(
            encryptedData = encryptedData,
            encryptedKey = encryptedKey,
            iv = iv,
            timestamp = timestamp
        )
    }

    /**
     * 解密API响应
     */
    fun decryptResponse(
        encryptedResponse: EncryptedRequest,
        serverPublicKey: PublicKey,
        aesKey: ByteArray
    ): String {
        // 1. 验证时间戳(防止重放攻击)
        val currentTime = System.currentTimeMillis()
        if (currentTime - encryptedResponse.timestamp > 300000) { // 5分钟
            throw SecurityException("请求已过期")
        }

        // 2. 验证签名(如果有)
        if (encryptedResponse.signature != null) {
            val dataToVerify = "${encryptedResponse.encryptedData}${encryptedResponse.timestamp}"
            if (!RSACrypto.verify(dataToVerify, encryptedResponse.signature, serverPublicKey)) {
                throw SecurityException("签名验证失败")
            }
        }

        // 3. 使用AES解密数据
        return AESCrypto.decrypt(encryptedResponse.encryptedData, aesKey, encryptedResponse.iv)
    }
}

// 使用示例
class ApiClient {
    private val serverPublicKey: PublicKey by lazy {
        // 从Native层获取服务器公钥,防止被篡改
        NativeKeyStore.getServerPublicKey()
    }

    suspend fun sendSecureRequest(endpoint: String, data: Map<String, Any>): String {
        val jsonData = Json.encodeToString(data)
        val encryptedRequest = HybridCrypto.encryptRequest(jsonData, serverPublicKey)

        val response = httpClient.post(endpoint) {
            setBody(encryptedRequest)
        }

        // 解密响应
        val aesKey = /* 保存的AES密钥 */
        return HybridCrypto.decryptResponse(response, serverPublicKey, aesKey)
    }
}

接口加密方案对比

方案优点缺点适用场景
仅HTTPS简单易用,性能好中间人攻击风险,证书可被绕过低敏感度应用
HTTPS + 签名防篡改,验证身份不防窃听,密钥易泄露一般应用
HTTPS + 对称加密性能好,防窃听密钥分发困难,易被逆向中等敏感度
混合加密安全性高,密钥动态实现复杂,性能开销大高敏感度应用

接口加密的主要弊端

1. 性能开销
// 加密操作会增加请求延迟
// 加密: 20-50ms
// 解密: 20-50ms
// RSA操作: 5-20ms
// 总延迟增加: ~50-120ms
2. 密钥管理复杂
  • 密钥存储安全性问题
  • 密钥轮换机制复杂
  • 多环境密钥管理困难
3. 调试困难
// 无法直接查看网络请求内容
// 需要专门的解密工具
// 增加开发和测试成本
4. 易被绕过
  • 客户端代码可被逆向
  • 密钥可被从内存中提取
  • Hook框架可拦截加密前的数据
  • 证书绑定可被绕过
5. 兼容性问题
// 不同平台加密库实现差异
// 加密算法版本兼容性
// 升级困难

安全建议

推荐做法:

  • HTTPS是基础,必须启用
  • 敏感接口使用混合加密
  • 实施证书锁定(Certificate Pinning)
  • 添加请求签名和时间戳
  • 使用Native层保护密钥
  • 实施多层防御策略

避免:

  • 将加密视为唯一安全手段
  • 在代码中硬编码密钥
  • 使用过时的加密算法(DES、MD5)
  • 忽视服务端安全
  • 过度依赖客户端加密

移动开发密钥保护

逆向工具威胁概述

工具类型威胁级别防护难度
Xposed/Frida动态Hook极高困难
jadx/JEB静态反编译中等
IDA Pro静态/动态分析极高非常困难
Charles网络抓包容易

防护策略

1. 代码混淆(ProGuard/R8)

build.gradle 配置

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

proguard-rules.pro

# 加密类不混淆名称但混淆内容
-keep class com.example.crypto.** { *; }
-keepclassmembers class com.example.crypto.** { *; }

# 混淆字符串
-adaptclassstrings
-obfuscationdictionary dictionary.txt
-classobfuscationdictionary dictionary.txt
-packageobfuscationdictionary dictionary.txt
2. Native层密钥保护(C++/NDK)

keys.cpp - 密钥分片与环境检测

#include <jni.h>
#include <string>
#include <android/log.h>
#include <sys/system_properties.h>
#include <unistd.h>

// 密钥分片存储(实际应用中应更复杂)
static const char KEY_PART1[] = {0x3A, 0x45, 0x7F, 0x2B, 0x1C, 0x9E};
static const char KEY_PART2[] = {0x6D, 0x8A, 0x4F, 0x3C, 0x5E, 0x2A};
static const char KEY_PART3[] = {0x9B, 0x7C, 0x2E, 0x4D, 0x6F, 0x1A};

// 环境检测
bool isEmulator() {
    char prop[PROP_VALUE_MAX];
    __system_property_get("ro.kernel.qemu", prop);
    return strcmp(prop, "1") == 0;
}

bool isRooted() {
    const char* paths[] = {
        "/system/app/Superuser.apk",
        "/system/xbin/su",
        "/system/bin/su",
        "/sbin/su"
    };

    for (const char* path : paths) {
        if (access(path, F_OK) == 0) return true;
    }
    return false;
}

bool isFridaRunning() {
    FILE* fp = fopen("/proc/self/maps", "r");
    if (fp) {
        char line[256];
        while (fgets(line, sizeof(line), fp)) {
            if (strstr(line, "frida") || strstr(line, "gum-js-loop")) {
                fclose(fp);
                return true;
            }
        }
        fclose(fp);
    }
    return false;
}

// 动态组合密钥
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_example_crypto_NativeKeyStore_getKey(JNIEnv* env, jobject) {
    // 1. 环境检测
    if (isEmulator() || isRooted() || isFridaRunning()) {
        __android_log_print(ANDROID_LOG_ERROR, "Security", "Unsafe environment detected");
        return nullptr; // 返回nullptr或假密钥
    }

    // 2. 动态组合密钥
    const int keySize = sizeof(KEY_PART1) + sizeof(KEY_PART2) + sizeof(KEY_PART3);
    char* key = new char[keySize];

    // 混淆组合顺序
    int offset = 0;
    memcpy(key + offset, KEY_PART1, sizeof(KEY_PART1));
    offset += sizeof(KEY_PART1);
    memcpy(key + offset, KEY_PART3, sizeof(KEY_PART3)); // 故意打乱顺序
    offset += sizeof(KEY_PART3);
    memcpy(key + offset, KEY_PART2, sizeof(KEY_PART2));

    // 3. XOR混淆
    for (int i = 0; i < keySize; i++) {
        key[i] ^= 0xAA ^ (i % 256);
    }

    // 4. 转换为jbyteArray
    jbyteArray result = env->NewByteArray(keySize);
    env->SetByteArrayRegion(result, 0, keySize, reinterpret_cast<jbyte*>(key));

    delete[] key;
    return result;
}

// 内联汇编反调试
extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_crypto_NativeKeyStore_isDebugging(JNIEnv* env, jobject) {
    #ifdef __arm__
    // ARM反调试
    register int r0 asm("r0");
    asm volatile(
        "mov r0, #0\n"
        "mov r7, #0x1a\n" // getpid syscall
        "svc #0\n"
        "mov r7, #0x2a\n" // ptrace syscall
        "svc #0\n"
    );
    return r0 == -1;
    #elif __aarch64__
    // ARM64反调试
    register long x0 asm("x0");
    asm volatile(
        "mov x0, #0\n"
        "mov x8, #0xac\n" // ptrace syscall
        "svc #0\n"
    );
    return x0 == -1;
    #else
    return false;
    #endif
}

Kotlin调用Native层

object NativeKeyStore {
    init {
        System.loadLibrary("native-crypto")
    }

    external fun getKey(): ByteArray?
    external fun isDebugging(): Boolean

    fun getSecureKey(): ByteArray {
        if (isDebugging()) {
            throw SecurityException("Debugging detected")
        }
        return getKey() ?: throw SecurityException("Failed to get key")
    }
}
3. 白盒加密(Rust)

概念:将密钥融入加密算法实现中,使得即使攻击者完全掌握代码也无法提取密钥。

// Rust实现白盒AES示例(简化版)
pub struct WhiteBoxAES {
    lookup_tables: Vec<Vec<u8>>,
}

impl WhiteBoxAES {
    // 预计算包含密钥的查找表
    pub fn new(key: &[u8; 16]) -> Self {
        let mut tables = Vec::new();

        // 生成混淆的查找表(密钥已融入)
        for round in 0..10 {
            let table = Self::generate_round_table(key, round);
            tables.push(table);
        }

        WhiteBoxAES {
            lookup_tables: tables,
        }
    }

    fn generate_round_table(key: &[u8; 16], round: usize) -> Vec<u8> {
        // 将密钥和S盒组合成查找表
        // 实际实现需要更复杂的混淆技术
        let mut table = vec![0u8; 256 * 16];

        // 将密钥融入查找表
        for i in 0..256 {
            for j in 0..16 {
                // 混淆逻辑:S盒 + 密钥 + 轮常数
                let sbox_val = Self::sbox(i as u8);
                let key_byte = key[j % 16];
                let round_const = (round as u8).wrapping_mul(j as u8);

                table[i * 16 + j] = sbox_val ^ key_byte ^ round_const;
            }
        }

        table
    }

    fn sbox(x: u8) -> u8 {
        // 简化的S盒实现
        // 实际应使用标准AES S盒
        x.wrapping_mul(251) ^ 99
    }

    pub fn encrypt(&self, plaintext: &[u8; 16]) -> [u8; 16] {
        let mut state = *plaintext;

        // 使用预计算的查找表进行加密
        for round_tables in &self.lookup_tables {
            state = self.apply_round(&state, round_tables);
        }

        state
    }

    fn apply_round(&self, state: &[u8; 16], table: &[u8]) -> [u8; 16] {
        let mut result = [0u8; 16];
        for i in 0..16 {
            result[i] = table[state[i] as usize * 16 + i];
        }
        result
    }
}

// FFI导出供移动端调用
#[no_mangle]
pub extern "C" fn whitebox_aes_encrypt(
    plaintext: *const u8,
    output: *mut u8
) {
    // 移动端调用接口
}
4. Android Keystore系统
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.Cipher

object AndroidKeystoreManager {
    private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
    private const val KEY_ALIAS = "app_master_key"

    /**
     * 生成或获取密钥
     */
    fun getOrCreateKey(): SecretKey {
        val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER)
        keyStore.load(null)

        // 如果密钥已存在,直接返回
        if (keyStore.containsAlias(KEY_ALIAS)) {
            return keyStore.getKey(KEY_ALIAS, null) as SecretKey
        }

        // 生成新密钥
        val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES,
            KEYSTORE_PROVIDER
        )

        val spec = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(256)
            // 需要生物识别认证才能使用密钥
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationValidityDurationSeconds(30)
            // 密钥不可导出
            .setRandomizedEncryptionRequired(true)
            .build()

        keyGenerator.init(spec)
        return keyGenerator.generateKey()
    }

    /**
     * 使用Keystore密钥加密
     */
    fun encryptWithKeystore(data: String): Pair<ByteArray, ByteArray> {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val key = getOrCreateKey()
        cipher.init(Cipher.ENCRYPT_MODE, key)

        val iv = cipher.iv
        val encrypted = cipher.doFinal(data.toByteArray())

        return Pair(encrypted, iv)
    }
}
5. 运行时检测与防护(Kotlin)
import android.os.Debug
import java.io.File
import java.net.Socket

object SecurityChecker {

    /**
     * 检测Xposed框架
     */
    fun isXposedInstalled(context: Context): Boolean {
        try {
            // 检测XposedBridge类
            Class.forName("de.robv.android.xposed.XposedBridge")
            return true
        } catch (e: ClassNotFoundException) {
            // 检测已安装的Xposed应用
            val xposedPackages = listOf(
                "de.robv.android.xposed.installer",
                "io.va.exposed",
                "org.meowcat.edxposed.manager"
            )

            val pm = context.packageManager
            return xposedPackages.any {
                try {
                    pm.getPackageInfo(it, 0)
                    true
                } catch (e: Exception) {
                    false
                }
            }
        }
    }

    /**
     * 检测Frida
     */
    fun isFridaRunning(): Boolean {
        // 1. 检测端口
        val fridaPorts = listOf(27042, 27043)
        for (port in fridaPorts) {
            try {
                Socket("127.0.0.1", port).use {
                    return true
                }
            } catch (e: Exception) {
                // Port not open
            }
        }

        // 2. 检测进程名
        val fridaProcessNames = listOf("frida-server", "frida-agent", "gum-js-loop")
        val processes = File("/proc").listFiles() ?: return false

        for (process in processes) {
            if (!process.name.matches(Regex("\\d+"))) continue

            val cmdline = File(process, "cmdline")
            if (cmdline.exists()) {
                try {
                    val content = cmdline.readText()
                    if (fridaProcessNames.any { content.contains(it) }) {
                        return true
                    }
                } catch (e: Exception) {
                    // 忽略无法读取的进程
                }
            }
        }

        // 3. 检测SO库
        try {
            val mapsFile = File("/proc/self/maps")
            if (mapsFile.exists()) {
                val maps = mapsFile.readText()
                if (maps.contains("frida") || maps.contains("gum-js-loop")) {
                    return true
                }
            }
        } catch (e: Exception) {
            // 忽略
        }

        return false
    }

    /**
     * 检测调试器
     */
    fun isDebuggerAttached(): Boolean {
        return Debug.isDebuggerConnected() ||
               Debug.waitingForDebugger() ||
               isNativeDebuggerAttached()
    }

    private fun isNativeDebuggerAttached(): Boolean {
        try {
            val tracerPid = File("/proc/self/status").readLines()
                .find { it.startsWith("TracerPid:") }
                ?.substringAfter(":")
                ?.trim()
                ?.toIntOrNull()

            return tracerPid != null && tracerPid > 0
        } catch (e: Exception) {
            return false
        }
    }

    /**
     * 完整性校验
     */
    fun verifyAppIntegrity(context: Context): Boolean {
        try {
            val packageInfo = context.packageManager.getPackageInfo(
                context.packageName,
                PackageManager.GET_SIGNATURES
            )

            val signature = packageInfo.signatures[0]
            val md = MessageDigest.getInstance("SHA-256")
            val digest = md.digest(signature.toByteArray())
            val hexDigest = digest.joinToString("") { "%02x".format(it) }

            // 与预期签名对比(将签名哈希存储在Native层)
            val expectedSignature = NativeKeyStore.getExpectedSignature()
            return hexDigest == expectedSignature
        } catch (e: Exception) {
            return false
        }
    }

    /**
     * 综合安全检查
     */
    fun performSecurityCheck(context: Context): SecurityCheckResult {
        return SecurityCheckResult(
            isXposed = isXposedInstalled(context),
            isFrida = isFridaRunning(),
            isDebugger = isDebuggerAttached(),
            isIntegrityValid = verifyAppIntegrity(context),
            isRooted = checkRoot()
        )
    }

    private fun checkRoot(): Boolean {
        // 使用RootBeer等库或自定义检测
        val rootPaths = listOf(
            "/system/app/Superuser.apk",
            "/system/xbin/su",
            "/system/bin/su"
        )
        return rootPaths.any { File(it).exists() }
    }
}

data class SecurityCheckResult(
    val isXposed: Boolean,
    val isFrida: Boolean,
    val isDebugger: Boolean,
    val isIntegrityValid: Boolean,
    val isRooted: Boolean
) {
    val isSafe: Boolean
        get() = !isXposed && !isFrida && !isDebugger && isIntegrityValid && !isRooted
}
6. 证书锁定(Certificate Pinning)
import okhttp3.CertificatePinner
import okhttp3.OkHttpClient

object SecureHttpClient {

    fun createClient(): OkHttpClient {
        // SHA-256 公钥指纹
        val certificatePinner = CertificatePinner.Builder()
            .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
            .add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=") // 备用证书
            .build()

        return OkHttpClient.Builder()
            .certificatePinner(certificatePinner)
            .addInterceptor { chain ->
                // 添加额外的安全检查
                if (SecurityChecker.isFridaRunning()) {
                    throw SecurityException("Security threat detected")
                }
                chain.proceed(chain.request())
            }
            .build()
    }
}

多层防护架构

┌─────────────────────────────────────────┐
│         应用层防护                        │
│  - 代码混淆                              │
│  - 字符串加密                            │
│  - 控制流平坦化                          │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│         运行时检测                        │
│  - Root检测                              │
│  - Xposed检测                            │
│  - Frida检测                             │
│  - 调试器检测                            │
│  - 完整性校验                            │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│         Native层防护                     │
│  - 密钥分片存储                          │
│  - 反调试机制                            │
│  - 内联汇编                              │
│  - SO加壳                                │
└──────────────┬──────────────────────────┘
               │
┌──────────────▼──────────────────────────┐
│         系统级防护                        │
│  - Android Keystore                     │
│  - 硬件支持的加密                        │
│  - 生物识别认证                          │
└─────────────────────────────────────────┘

最佳实践总结

应该做的
  1. 多层防御:不依赖单一防护手段
  2. 深度隐藏:密钥存储在Native层,使用白盒加密
  3. 动态检测:运行时持续检测威胁环境
  4. 快速响应:检测到威胁立即采取措施
  5. 定期更新:更新检测规则和加密策略
  6. 混淆加固:代码、字符串、资源全面混淆
  7. 完整性保护:签名校验、防二次打包
不应该做的
  1. 在Java/Kotlin层直接硬编码密钥
  2. 认为某一种防护措施足够安全
  3. 忽视服务端安全
  4. 在日志中输出敏感信息
  5. 使用过时的加密算法
  6. 开发环境和生产环境使用相同密钥

工具推荐

用途工具说明
代码混淆ProGuard/R8, DexGuardAndroid代码混淆
SO加壳梆梆加固, 360加固, 腾讯云加固Native层保护
Root检测RootBeer, SafetyNet检测设备Root状态
完整性检测Play Integrity APIGoogle官方完整性验证
白盒加密自研或商业方案密钥白盒化

总结

移动端安全是一个持续对抗的过程,没有绝对的安全,只有相对的安全。通过合理组合:

  • 加密技术(对称+非对称混合)
  • 密钥保护(Native层+白盒+Keystore)
  • 运行时检测(多种威胁检测)
  • 代码混淆(多层混淆加固)
  • 完整性保护(签名+校验)

可以大幅提高攻击成本,保护应用和用户数据安全。

记住:安全防护的目标不是让破解变得不可能,而是让破解成本远高于收益。


参考资源