第十四章:防护设计最佳实践
本章字数:约17000字 阅读时间:约60分钟 难度等级:★★★★☆
声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理。文中的"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。
引言
经过前面章节的攻防分析,我们已经深入了解了移动应用面临的安全威胁和各种攻击手段。本章将从防御者的角度,系统性地介绍如何设计和实现安全的移动应用。
14.1 安全架构设计原则
14.1.1 纵深防御
┌─────────────────────────────────────────────────────────────────┐
│ 纵深防御架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 第一层:网络层 │ │
│ │ • HTTPS强制 │ │
│ │ • 证书固定 │ │
│ │ • 代理检测 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 第二层:应用层 │ │
│ │ • 代码混淆 │ │
│ │ • 完整性校验 │ │
│ │ • 环境检测 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 第三层:业务层 │ │
│ │ • 请求签名 │ │
│ │ • 参数加密 │ │
│ │ • 防重放 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 第四层:数据层 │ │
│ │ • 本地数据加密 │ │
│ │ • 密钥安全存储 │ │
│ │ • 敏感数据保护 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 第五层:Native层 │ │
│ │ • 关键逻辑Native化 │ │
│ │ • 反调试保护 │ │
│ │ • 代码加固 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 设计原则: │
│ • 每层独立防护,不依赖其他层 │
│ • 单层被突破不会导致全面失守 │
│ • 攻击者需要突破所有层才能达成目标 │
│ │
└─────────────────────────────────────────────────────────────────┘
14.1.2 最小权限原则
/**
* 最小权限设计示例
*/
public class MinimalPrivilegeDesign {
/**
* 权限申请最佳实践
*/
public void requestPermissions() {
// 1. 只申请必要的权限
// 不要申请"以防万一"的权限
// 2. 延迟申请
// 在真正需要时才申请,而不是启动时全部申请
// 3. 解释原因
// 向用户说明为什么需要这个权限
// 4. 优雅降级
// 用户拒绝权限时,提供替代方案
}
/**
* 数据访问最小化
*/
public void minimalDataAccess() {
// 1. 只收集必要的数据
// 2. 数据本地处理优先
// 3. 及时清理不需要的数据
// 4. 敏感数据脱敏处理
}
/**
* 组件暴露最小化
*/
public void minimalComponentExposure() {
// 1. Activity/Service/Receiver默认不导出
// android:exported="false"
// 2. ContentProvider设置权限
// android:readPermission="..."
// android:writePermission="..."
// 3. Intent过滤器谨慎使用
// 避免隐式Intent被恶意应用利用
}
}
14.1.3 安全默认值
<!-- AndroidManifest.xml 安全配置 -->
<application
android:allowBackup="false"
android:usesCleartextTraffic="false"
android:networkSecurityConfig="@xml/network_security_config"
android:debuggable="false">
<!-- 组件默认不导出 -->
<activity
android:name=".MainActivity"
android:exported="true">
<!-- 只有主Activity需要导出 -->
</activity>
<activity
android:name=".InternalActivity"
android:exported="false" />
<service
android:name=".BackgroundService"
android:exported="false" />
<receiver
android:name=".InternalReceiver"
android:exported="false" />
<provider
android:name=".DataProvider"
android:exported="false"
android:grantUriPermissions="false" />
</application>
14.2 网络安全设计
14.2.1 传输层安全
<!-- 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.dreamworld.com</domain>
<!-- 证书固定 -->
<pin-set expiration="2027-01-01">
<!-- 主证书 -->
<pin digest="SHA-256">base64EncodedSHA256Hash1=</pin>
<!-- 备用证书 -->
<pin digest="SHA-256">base64EncodedSHA256Hash2=</pin>
</pin-set>
</domain-config>
<!-- 调试配置(仅debug版本) -->
<debug-overrides>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
14.2.2 请求签名设计
/**
* API请求签名设计
*/
public class ApiSignatureDesign {
/**
* 签名方案设计
*/
public static class SignatureScheme {
// 签名要素
// 1. 时间戳 - 防止重放
// 2. 随机数 - 增加唯一性
// 3. 请求参数 - 防止篡改
// 4. 设备标识 - 绑定设备
// 5. 密钥 - 验证身份
/**
* 构建签名字符串
*/
public String buildSignatureString(Request request) {
StringBuilder sb = new StringBuilder();
// 按固定顺序拼接
sb.append(request.getMethod()).append("\n");
sb.append(request.getPath()).append("\n");
sb.append(request.getTimestamp()).append("\n");
sb.append(request.getNonce()).append("\n");
sb.append(request.getDeviceId()).append("\n");
// 参数排序后拼接
Map<String, String> params = new TreeMap<>(request.getParams());
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
// Body的MD5
if (request.getBody() != null) {
sb.append(md5(request.getBody()));
}
return sb.toString();
}
/**
* 计算签名
*/
public String sign(String data, String key) {
// 使用HMAC-SHA256
return hmacSha256(data, key);
}
}
/**
* 防重放设计
*/
public static class AntiReplay {
// 服务端检查
// 1. 时间戳有效期(如5分钟内)
// 2. Nonce唯一性(Redis存储已使用的Nonce)
// 3. 签名有效性
/**
* 验证请求
*/
public boolean validateRequest(Request request) {
// 1. 检查时间戳
long now = System.currentTimeMillis();
long timestamp = request.getTimestamp();
if (Math.abs(now - timestamp) > 5 * 60 * 1000) {
return false; // 时间戳过期
}
// 2. 检查Nonce
String nonce = request.getNonce();
if (isNonceUsed(nonce)) {
return false; // Nonce已使用
}
markNonceUsed(nonce);
// 3. 验证签名
String expectedSign = calculateSign(request);
if (!expectedSign.equals(request.getSign())) {
return false; // 签名无效
}
return true;
}
}
}
14.2.3 数据加密传输
/**
* 敏感数据加密传输
*/
public class DataEncryption {
/**
* 请求加密
*/
public String encryptRequest(String plainText, String aesKey) {
// 1. 生成随机IV
byte[] iv = generateRandomIV();
// 2. AES-GCM加密
byte[] encrypted = aesGcmEncrypt(plainText.getBytes(), aesKey, iv);
// 3. 组合IV和密文
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
// 4. Base64编码
return Base64.encodeToString(result, Base64.NO_WRAP);
}
/**
* 响应解密
*/
public String decryptResponse(String cipherText, String aesKey) {
// 1. Base64解码
byte[] data = Base64.decode(cipherText, Base64.NO_WRAP);
// 2. 分离IV和密文
byte[] iv = Arrays.copyOfRange(data, 0, 12);
byte[] encrypted = Arrays.copyOfRange(data, 12, data.length);
// 3. AES-GCM解密
byte[] decrypted = aesGcmDecrypt(encrypted, aesKey, iv);
return new String(decrypted);
}
/**
* 密钥协商
*/
public byte[] keyExchange(PublicKey serverPublicKey) {
// 使用ECDH进行密钥协商
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair clientKeyPair = keyGen.generateKeyPair();
// 计算共享密钥
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(clientKeyPair.getPrivate());
keyAgreement.doPhase(serverPublicKey, true);
byte[] sharedSecret = keyAgreement.generateSecret();
// 派生AES密钥
return deriveKey(sharedSecret, "AES", 256);
}
}
14.3 代码保护策略
14.3.1 混淆策略
# proguard-rules.pro - 生产级混淆配置
# 基础优化
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
# 保留必要的类
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.view.View
# 保留JNI方法
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# 保留序列化
-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();
}
# 保留枚举
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留注解
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
# 混淆字典
-obfuscationdictionary proguard-dictionary.txt
-classobfuscationdictionary proguard-dictionary.txt
-packageobfuscationdictionary proguard-dictionary.txt
# 重新打包到根包
-repackageclasses ''
-allowaccessmodification
# 移除日志
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
14.3.2 关键代码Native化
/**
* 关键逻辑Native化设计
*/
public class NativeSecurityModule {
static {
System.loadLibrary("security");
}
// 密钥生成 - Native实现
public static native byte[] generateKey(int length);
// 签名计算 - Native实现
public static native String calculateSignature(String data, byte[] key);
// 加密操作 - Native实现
public static native byte[] encrypt(byte[] data, byte[] key);
// 解密操作 - Native实现
public static native byte[] decrypt(byte[] data, byte[] key);
// 完整性校验 - Native实现
public static native boolean verifyIntegrity();
// 环境检测 - Native实现
public static native boolean isSecureEnvironment();
}
// security.c - Native安全模块实现
#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
// 内部密钥(实际应用中应更安全地存储)
static const unsigned char INTERNAL_KEY[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};
// 生成随机密钥
JNIEXPORT jbyteArray JNICALL
Java_com_dreamworld_NativeSecurityModule_generateKey(
JNIEnv *env, jclass clazz, jint length) {
unsigned char *key = malloc(length);
if (RAND_bytes(key, length) != 1) {
free(key);
return NULL;
}
jbyteArray result = (*env)->NewByteArray(env, length);
(*env)->SetByteArrayRegion(env, result, 0, length, (jbyte*)key);
// 清除内存
memset(key, 0, length);
free(key);
return result;
}
// 计算HMAC签名
JNIEXPORT jstring JNICALL
Java_com_dreamworld_NativeSecurityModule_calculateSignature(
JNIEnv *env, jclass clazz, jstring data, jbyteArray key) {
const char *dataStr = (*env)->GetStringUTFChars(env, data, NULL);
jbyte *keyBytes = (*env)->GetByteArrayElements(env, key, NULL);
jsize keyLen = (*env)->GetArrayLength(env, key);
unsigned char result[EVP_MAX_MD_SIZE];
unsigned int resultLen;
HMAC(EVP_sha256(), keyBytes, keyLen,
(unsigned char*)dataStr, strlen(dataStr),
result, &resultLen);
// 转换为十六进制字符串
char hexResult[resultLen * 2 + 1];
for (int i = 0; i < resultLen; i++) {
sprintf(hexResult + i * 2, "%02x", result[i]);
}
(*env)->ReleaseStringUTFChars(env, data, dataStr);
(*env)->ReleaseByteArrayElements(env, key, keyBytes, 0);
return (*env)->NewStringUTF(env, hexResult);
}
// AES加密
JNIEXPORT jbyteArray JNICALL
Java_com_dreamworld_NativeSecurityModule_encrypt(
JNIEnv *env, jclass clazz, jbyteArray data, jbyteArray key) {
jbyte *dataBytes = (*env)->GetByteArrayElements(env, data, NULL);
jsize dataLen = (*env)->GetArrayLength(env, data);
jbyte *keyBytes = (*env)->GetByteArrayElements(env, key, NULL);
// 生成IV
unsigned char iv[16];
RAND_bytes(iv, 16);
// 加密
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL,
(unsigned char*)keyBytes, iv);
int outLen;
unsigned char *outBuf = malloc(dataLen + 16 + 16); // IV + 数据 + Tag
// 复制IV
memcpy(outBuf, iv, 16);
// 加密数据
EVP_EncryptUpdate(ctx, outBuf + 16, &outLen,
(unsigned char*)dataBytes, dataLen);
int totalLen = 16 + outLen;
EVP_EncryptFinal_ex(ctx, outBuf + totalLen, &outLen);
totalLen += outLen;
// 获取Tag
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, outBuf + totalLen);
totalLen += 16;
EVP_CIPHER_CTX_free(ctx);
jbyteArray result = (*env)->NewByteArray(env, totalLen);
(*env)->SetByteArrayRegion(env, result, 0, totalLen, (jbyte*)outBuf);
free(outBuf);
(*env)->ReleaseByteArrayElements(env, data, dataBytes, 0);
(*env)->ReleaseByteArrayElements(env, key, keyBytes, 0);
return result;
}
14.4 密钥管理
14.4.1 密钥存储方案
┌─────────────────────────────────────────────────────────────────┐
│ 密钥存储方案对比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 方案 安全性 易用性 适用场景 │
│ ───────────────────────────────────────────────────────────── │
│ 硬编码 ★☆☆☆☆ ★★★★★ 不推荐 │
│ │
│ SharedPreferences ★★☆☆☆ ★★★★☆ 非敏感配置 │
│ │
│ 加密文件 ★★★☆☆ ★★★☆☆ 一般敏感数据 │
│ │
│ Android Keystore ★★★★☆ ★★★☆☆ 密钥存储 │
│ │
│ TEE/SE ★★★★★ ★★☆☆☆ 高安全场景 │
│ │
│ 服务端存储 ★★★★★ ★★☆☆☆ 最敏感数据 │
│ │
└─────────────────────────────────────────────────────────────────┘
14.4.2 Android Keystore使用
/**
* Android Keystore安全存储
*/
public class SecureKeyStore {
private static final String KEY_ALIAS = "DreamWorldSecureKey";
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
/**
* 生成密钥
*/
public void generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
// 需要用户认证
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(30)
// 绑定到安全硬件
.setIsStrongBoxBacked(true)
.build();
keyGenerator.init(spec);
keyGenerator.generateKey();
}
/**
* 加密数据
*/
public byte[] encrypt(byte[] data) throws Exception {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(data);
// 组合IV和密文
byte[] result = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
return result;
}
/**
* 解密数据
*/
public byte[] decrypt(byte[] encryptedData) throws Exception {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
// 分离IV和密文
byte[] iv = Arrays.copyOfRange(encryptedData, 0, 12);
byte[] encrypted = Arrays.copyOfRange(encryptedData, 12, encryptedData.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
return cipher.doFinal(encrypted);
}
/**
* 检查密钥是否存在
*/
public boolean keyExists() {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
return keyStore.containsAlias(KEY_ALIAS);
} catch (Exception e) {
return false;
}
}
/**
* 删除密钥
*/
public void deleteKey() throws Exception {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
keyStore.deleteEntry(KEY_ALIAS);
}
}
14.4.3 密钥轮换策略
/**
* 密钥轮换管理
*/
public class KeyRotationManager {
private static final long KEY_VALIDITY_DAYS = 90; // 密钥有效期90天
/**
* 检查是否需要轮换密钥
*/
public boolean needsRotation() {
long keyCreationTime = getKeyCreationTime();
long now = System.currentTimeMillis();
long validityMs = KEY_VALIDITY_DAYS * 24 * 60 * 60 * 1000L;
return (now - keyCreationTime) > validityMs;
}
/**
* 执行密钥轮换
*/
public void rotateKey() {
// 1. 生成新密钥
String newKeyId = generateNewKey();
// 2. 使用新密钥重新加密数据
reEncryptData(newKeyId);
// 3. 通知服务端更新密钥
notifyServerKeyRotation(newKeyId);
// 4. 删除旧密钥
deleteOldKey();
// 5. 更新密钥创建时间
updateKeyCreationTime();
}
/**
* 双密钥过渡期
* 在轮换期间同时支持新旧密钥
*/
public byte[] decryptWithFallback(byte[] data) {
try {
// 尝试用新密钥解密
return decryptWithNewKey(data);
} catch (Exception e) {
// 回退到旧密钥
return decryptWithOldKey(data);
}
}
}
14.5 运行时保护
14.5.1 环境检测
/**
* 运行环境安全检测
*/
public class EnvironmentChecker {
/**
* 综合环境检测
*/
public SecurityCheckResult checkEnvironment(Context context) {
SecurityCheckResult result = new SecurityCheckResult();
// 1. Root检测
result.isRooted = checkRoot();
// 2. 模拟器检测
result.isEmulator = checkEmulator();
// 3. 调试检测
result.isDebugged = checkDebug(context);
// 4. Hook框架检测
result.hasHookFramework = checkHookFramework();
// 5. 签名校验
result.isSignatureValid = checkSignature(context);
// 6. 安装来源检测
result.isFromTrustedSource = checkInstallSource(context);
return result;
}
/**
* Root检测
*/
private boolean checkRoot() {
// 检查常见Root文件
String[] rootPaths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/app/Superuser.apk",
"/system/app/SuperSU.apk"
};
for (String path : rootPaths) {
if (new File(path).exists()) {
return true;
}
}
// 检查Magisk
if (new File("/sbin/.magisk").exists() ||
new File("/data/adb/magisk").exists()) {
return true;
}
// 检查su命令
try {
Process process = Runtime.getRuntime().exec("which su");
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
if (reader.readLine() != null) {
return true;
}
} catch (Exception e) {
// ignore
}
return false;
}
/**
* 模拟器检测
*/
private boolean checkEmulator() {
// 检查Build属性
if (Build.FINGERPRINT.contains("generic") ||
Build.FINGERPRINT.contains("unknown") ||
Build.MODEL.contains("google_sdk") ||
Build.MODEL.contains("Emulator") ||
Build.MODEL.contains("Android SDK built for x86") ||
Build.MANUFACTURER.contains("Genymotion") ||
Build.HARDWARE.contains("goldfish") ||
Build.HARDWARE.contains("ranchu") ||
Build.PRODUCT.contains("sdk") ||
Build.PRODUCT.contains("vbox86p") ||
Build.BOARD.contains("unknown")) {
return true;
}
// 检查特征文件
String[] emulatorFiles = {
"/dev/socket/qemud",
"/dev/qemu_pipe",
"/system/lib/libc_malloc_debug_qemu.so",
"/sys/qemu_trace",
"/system/bin/qemu-props"
};
for (String file : emulatorFiles) {
if (new File(file).exists()) {
return true;
}
}
return false;
}
/**
* 调试检测
*/
private boolean checkDebug(Context context) {
// 检查调试标志
if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
return true;
}
// 检查调试器连接
if (Debug.isDebuggerConnected()) {
return true;
}
return false;
}
/**
* Hook框架检测
*/
private boolean checkHookFramework() {
// 检查Xposed
try {
Class.forName("de.robv.android.xposed.XposedBridge");
return true;
} catch (ClassNotFoundException e) {
// not found
}
// 检查Frida
try {
// 检查Frida特征端口
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 27042), 100);
socket.close();
return true;
} catch (Exception e) {
// not found
}
// 检查堆栈中的Hook痕迹
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
String className = element.getClassName();
if (className.contains("xposed") ||
className.contains("frida") ||
className.contains("substrate")) {
return true;
}
}
return false;
}
/**
* 签名校验
*/
private boolean checkSignature(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(signature.toByteArray());
String signatureHash = bytesToHex(hash);
// 与预期签名比较
if (signatureHash.equals(EXPECTED_SIGNATURE)) {
return true;
}
}
} catch (Exception e) {
// error
}
return false;
}
private static final String EXPECTED_SIGNATURE = "...";
/**
* 安全检测结果
*/
public static class SecurityCheckResult {
public boolean isRooted;
public boolean isEmulator;
public boolean isDebugged;
public boolean hasHookFramework;
public boolean isSignatureValid;
public boolean isFromTrustedSource;
public boolean isSecure() {
return !isRooted && !isEmulator && !isDebugged &&
!hasHookFramework && isSignatureValid && isFromTrustedSource;
}
}
}
14.6 安全开发流程
14.6.1 安全开发生命周期
┌─────────────────────────────────────────────────────────────────┐
│ 安全开发生命周期 (SDL) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 阶段 安全活动 │
│ ───────────────────────────────────────────────────────────── │
│ 需求阶段 • 安全需求分析 │
│ • 威胁建模 │
│ • 安全目标定义 │
│ │
│ 设计阶段 • 安全架构设计 │
│ • 攻击面分析 │
│ • 安全设计评审 │
│ │
│ 开发阶段 • 安全编码规范 │
│ • 代码安全审查 │
│ • 静态代码分析 │
│ │
│ 测试阶段 • 安全测试 │
│ • 渗透测试 │
│ • 漏洞扫描 │
│ │
│ 发布阶段 • 安全配置检查 │
│ • 最终安全评审 │
│ • 应急响应计划 │
│ │
│ 运维阶段 • 安全监控 │
│ • 漏洞响应 │
│ • 安全更新 │
│ │
└─────────────────────────────────────────────────────────────────┘
14.6.2 安全编码规范
/**
* 安全编码规范示例
*/
public class SecureCodingGuidelines {
// ==================== 输入验证 ====================
/**
* 规则1: 始终验证输入
*/
public void validateInput(String input) {
// 检查null
if (input == null) {
throw new IllegalArgumentException("Input cannot be null");
}
// 检查长度
if (input.length() > MAX_LENGTH) {
throw new IllegalArgumentException("Input too long");
}
// 检查格式
if (!input.matches(VALID_PATTERN)) {
throw new IllegalArgumentException("Invalid input format");
}
// 检查特殊字符
input = sanitize(input);
}
// ==================== 敏感数据处理 ====================
/**
* 规则2: 敏感数据使用后立即清除
*/
public void handleSensitiveData() {
char[] password = getPassword();
try {
// 使用密码
authenticate(password);
} finally {
// 清除密码
Arrays.fill(password, '\0');
}
}
/**
* 规则3: 不要在日志中记录敏感信息
*/
public void secureLogging(String userId, String password) {
// 错误示例
// Log.d(TAG, "Login: user=" + userId + ", password=" + password);
// 正确示例
Log.d(TAG, "Login attempt for user: " + maskUserId(userId));
}
// ==================== 加密使用 ====================
/**
* 规则4: 使用安全的加密算法
*/
public void secureEncryption() {
// 错误: 使用弱算法
// Cipher.getInstance("DES");
// Cipher.getInstance("AES/ECB/PKCS5Padding");
// 正确: 使用强算法
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
}
/**
* 规则5: 使用安全的随机数
*/
public byte[] generateSecureRandom(int length) {
// 错误: 使用不安全的随机数
// Random random = new Random();
// 正确: 使用安全随机数
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[length];
secureRandom.nextBytes(bytes);
return bytes;
}
// ==================== 异常处理 ====================
/**
* 规则6: 不要泄露敏感信息到异常
*/
public void secureExceptionHandling() {
try {
// 业务逻辑
} catch (Exception e) {
// 错误: 暴露内部信息
// throw new RuntimeException("Database error: " + e.getMessage());
// 正确: 记录详细日志,返回通用错误
Log.e(TAG, "Internal error", e);
throw new RuntimeException("An error occurred. Please try again.");
}
}
}
14.7 本章小结
本章我们从防御者的角度,系统性地介绍了移动应用安全设计的最佳实践:
14.7.1 技术要点回顾
┌─────────────────────────────────────────────────────────────────┐
│ 本章技术要点 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 安全架构设计 │
│ • 纵深防御原则 │
│ • 最小权限原则 │
│ • 安全默认值 │
│ │
│ 2. 网络安全 │
│ • 传输层安全 │
│ • 请求签名设计 │
│ • 数据加密传输 │
│ │
│ 3. 代码保护 │
│ • 混淆策略 │
│ • 关键代码Native化 │
│ │
│ 4. 密钥管理 │
│ • 安全存储方案 │
│ • Android Keystore │
│ • 密钥轮换策略 │
│ │
│ 5. 运行时保护 │
│ • 环境检测 │
│ • 完整性校验 │
│ │
│ 6. 安全开发流程 │
│ • 安全开发生命周期 │
│ • 安全编码规范 │
│ │
└─────────────────────────────────────────────────────────────────┘
14.7.2 下一章预告
在下一章《逆向工程方法论》中,我们将总结逆向工程的思维方法和实践经验:
- 逆向工程的思维模式
- 问题分析方法
- 工具选择策略
- 经验总结
本章完