移动端安全攻防
对称加密
概述
对称加密使用相同的密钥进行加密和解密。常见算法包括 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
}
接口加解密应用
混合加密方案
在实际的移动应用开发中,通常采用对称加密 + 非对称加密的混合方案:
- 客户端生成随机AES密钥
- 使用AES密钥加密请求数据(快速)
- 使用服务器RSA公钥加密AES密钥(安全传输密钥)
- 同时发送加密数据和加密后的密钥
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 │
│ - 硬件支持的加密 │
│ - 生物识别认证 │
└─────────────────────────────────────────┘
最佳实践总结
应该做的
- 多层防御:不依赖单一防护手段
- 深度隐藏:密钥存储在Native层,使用白盒加密
- 动态检测:运行时持续检测威胁环境
- 快速响应:检测到威胁立即采取措施
- 定期更新:更新检测规则和加密策略
- 混淆加固:代码、字符串、资源全面混淆
- 完整性保护:签名校验、防二次打包
不应该做的
- 在Java/Kotlin层直接硬编码密钥
- 认为某一种防护措施足够安全
- 忽视服务端安全
- 在日志中输出敏感信息
- 使用过时的加密算法
- 开发环境和生产环境使用相同密钥
工具推荐
| 用途 | 工具 | 说明 |
|---|---|---|
| 代码混淆 | ProGuard/R8, DexGuard | Android代码混淆 |
| SO加壳 | 梆梆加固, 360加固, 腾讯云加固 | Native层保护 |
| Root检测 | RootBeer, SafetyNet | 检测设备Root状态 |
| 完整性检测 | Play Integrity API | Google官方完整性验证 |
| 白盒加密 | 自研或商业方案 | 密钥白盒化 |
总结
移动端安全是一个持续对抗的过程,没有绝对的安全,只有相对的安全。通过合理组合:
- 加密技术(对称+非对称混合)
- 密钥保护(Native层+白盒+Keystore)
- 运行时检测(多种威胁检测)
- 代码混淆(多层混淆加固)
- 完整性保护(签名+校验)
可以大幅提高攻击成本,保护应用和用户数据安全。
记住:安全防护的目标不是让破解变得不可能,而是让破解成本远高于收益。