第七章:完整调用链实现
本章字数:约30000字 阅读时间:约100分钟 难度等级:★★★★☆
声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理。文中的"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。
引言
经过前面六章的铺垫,我们已经:
- 了解了梦想世界APP的安全防护体系
- 掌握了Unidbg的使用方法
- 剖析了完整的JNI调用链
- 攻克了三大核心坑点
现在,是时候把所有组件整合起来,实现一个完整的、可运行的调用链了。
本章将从零开始,一步步构建完整的项目,最终实现:
- 自动化的安全激活流程
- 可靠的API请求签名
- 完整的数据获取能力
7.1 项目架构设计
7.1.1 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 项目整体架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Main │ │ 数据获取 │ │ 数据处理 │ │ │
│ │ │ 入口 │ │ 服务 │ │ 服务 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 业务层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 安全操作 │ │ API客户端 │ │ 数据模型 │ │ │
│ │ │ 服务 │ │ 封装 │ │ 定义 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 基础层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Unidbg │ │ HTTP │ │ 存储 │ │ │
│ │ │ 封装 │ │ 工具 │ │ 管理 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
7.1.2 目录结构
dreamworld-security-chain/
├── pom.xml # Maven配置
├── README.md # 项目说明
├── resources/
│ ├── app.apk # 目标APK(脱敏)
│ └── libs/
│ ├── libSecurityCore.so # 目标SO库
│ └── libc++_shared.so # 依赖库
├── src/main/java/com/dreamworld/
│ ├── Main.java # 主入口
│ ├── config/
│ │ └── AppConfig.java # 配置管理
│ ├── model/
│ │ ├── ActivationRequest.java # 激活请求模型
│ │ ├── ActivationResponse.java # 激活响应模型
│ │ ├── KeySuite.java # 密钥套件模型
│ │ └── ApiResponse.java # API响应模型
│ ├── network/
│ │ ├── HttpClient.java # HTTP客户端
│ │ ├── KeySuiteApiClient.java # 密钥套件API
│ │ └── BusinessApiClient.java # 业务API
│ ├── security/
│ │ ├── SecurityStub.java # 安全方法封装
│ │ ├── SecurityOperation.java # 安全操作服务
│ │ └── SignatureBuilder.java # 签名构造器
│ ├── storage/
│ │ ├── KeyStorage.java # 密钥存储接口
│ │ └── MemoryKeyStorage.java # 内存存储实现
│ ├── unidbg/
│ │ ├── UnidbgManager.java # Unidbg管理器
│ │ └── JNIBridge.java # JNI桥接
│ └── utils/
│ ├── LogUtils.java # 日志工具
│ ├── CryptoUtils.java # 加密工具
│ └── DeviceUtils.java # 设备工具
└── src/test/java/
└── com/dreamworld/
└── SecurityTest.java # 单元测试
7.1.3 Maven配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dreamworld</groupId>
<artifactId>security-chain</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>DreamWorld Security Chain</name>
<description>梦想世界APP安全调用链实现</description>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<unidbg.version>0.9.7</unidbg.version>
<gson.version>2.10.1</gson.version>
<okhttp.version>4.12.0</okhttp.version>
</properties>
<dependencies>
<!-- Unidbg -->
<dependency>
<groupId>com.github.zhkl0228</groupId>
<artifactId>unidbg-android</artifactId>
<version>${unidbg.version}</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- HTTP客户端 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>com.dreamworld.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
</project>
7.2 基础层实现
7.2.1 配置管理
package com.dreamworld.config;
import java.io.File;
/**
* 应用配置
*/
public class AppConfig {
// 资源路径
private static final String USER_HOME = System.getProperty("user.home");
private static final String RESOURCE_DIR = USER_HOME + "/Downloads/dreamworld";
// APK和SO库路径
public static final String APK_PATH = RESOURCE_DIR + "/app.apk";
public static final String LIB_DIR = RESOURCE_DIR + "/libs";
public static final String SECURITY_SO = "libSecurityCore.so";
public static final String CPP_SHARED_SO = "libc++_shared.so";
// API配置
public static final String API_HOST = "https://api.dreamworld.com";
public static final String KEY_SUITE_PATH = "/ddes/v1-0/app/key-suite";
// 设备信息(已脱敏)
public static final String DEVICE_ID = "a1b2c3d4e5f67890";
public static final String DEVICE_MODEL = "Pixel 6";
public static final String DEVICE_TYPE = "2";
public static final String MODEL_NAME = "ANDROID";
// APP信息
public static final String APP_VERSION = "8.x.x";
public static final String ENV = "prod";
// 超时配置
public static final int CONNECT_TIMEOUT = 30000;
public static final int READ_TIMEOUT = 30000;
/**
* 验证配置
*/
public static boolean validate() {
File apkFile = new File(APK_PATH);
if (!apkFile.exists()) {
System.err.println("APK文件不存在: " + APK_PATH);
return false;
}
File libDir = new File(LIB_DIR);
if (!libDir.exists() || !libDir.isDirectory()) {
System.err.println("库目录不存在: " + LIB_DIR);
return false;
}
File securitySo = new File(LIB_DIR, SECURITY_SO);
if (!securitySo.exists()) {
System.err.println("安全库不存在: " + securitySo.getPath());
return false;
}
return true;
}
}
7.2.2 日志工具
package com.dreamworld.utils;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日志工具类
*/
public class LogUtils {
private static boolean DEBUG = true;
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
public static void setDebug(boolean debug) {
DEBUG = debug;
}
public static void i(String tag, String msg) {
log("INFO", tag, msg);
}
public static void d(String tag, String msg) {
if (DEBUG) {
log("DEBUG", tag, msg);
}
}
public static void w(String tag, String msg) {
log("WARN", tag, msg);
}
public static void e(String tag, String msg) {
log("ERROR", tag, msg);
}
public static void e(String tag, String msg, Throwable t) {
log("ERROR", tag, msg);
if (DEBUG) {
t.printStackTrace();
}
}
private static void log(String level, String tag, String msg) {
String timestamp = DATE_FORMAT.format(new Date());
System.out.println(String.format("[%s] [%s] [%s] %s",
timestamp, level, tag, msg));
}
/**
* 打印分隔线
*/
public static void separator(String title) {
System.out.println();
System.out.println("═══════════════════════════════════════════════════════════");
System.out.println(" " + title);
System.out.println("═══════════════════════════════════════════════════════════");
}
/**
* 打印成功信息
*/
public static void success(String msg) {
System.out.println("✓ " + msg);
}
/**
* 打印失败信息
*/
public static void fail(String msg) {
System.out.println("✗ " + msg);
}
}
7.2.3 加密工具
package com.dreamworld.utils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
/**
* 加密工具类
*/
public class CryptoUtils {
/**
* 计算MD5并返回Base64编码
*
* 重要:梦想世界APP使用Base64编码,而非十六进制!
*/
public static String md5Base64(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(digest);
} catch (Exception e) {
LogUtils.e("CryptoUtils", "MD5计算失败", e);
return "";
}
}
/**
* 计算MD5并返回十六进制编码
*/
public static String md5Hex(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
LogUtils.e("CryptoUtils", "MD5计算失败", e);
return "";
}
}
/**
* 生成UUID(无连字符)
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 生成UUID(带连字符)
*/
public static String generateUUIDWithDash() {
return UUID.randomUUID().toString();
}
/**
* 生成时间戳(毫秒)
*/
public static String generateTimestamp() {
return String.valueOf(System.currentTimeMillis());
}
}
7.2.4 Unidbg管理器
package com.dreamworld.unidbg;
import com.dreamworld.config.AppConfig;
import com.dreamworld.utils.LogUtils;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.memory.Memory;
import java.io.File;
/**
* Unidbg管理器
* 负责初始化和管理Unidbg环境
*/
public class UnidbgManager extends AbstractJni {
private static final String TAG = "UnidbgManager";
private static UnidbgManager instance;
private AndroidEmulator emulator;
private VM vm;
private Module module;
private DvmClass jniWrapperClass;
private boolean initialized = false;
// JNI方法签名(已脱敏)
private static final String METHOD_SET_ENV =
"oSetEnv1a2b3c4d5e6f7a8b9c0d1e2f3a4(Ljava/lang/String;)V";
private static final String METHOD_GET_PRI_ID =
"oGetPriId2b3c4d5e6f7a8b9c0d1e2f3a4b()Ljava/lang/String;";
private static final String METHOD_RSA_SIGN =
"oRsaSign3c4d5e6f7a8b9c0d1e2f3a4b5c(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
private static final String METHOD_FORMAT_DK =
"oFormatDK4d5e6f7a8b9c0d1e2f3a4b5c(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;";
private static final String METHOD_HMAC_SIGN =
"oHmacSign5e6f7a8b9c0d1e2f3a4b5c6d(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
private UnidbgManager() {
// 私有构造函数
}
/**
* 获取单例实例
*/
public static synchronized UnidbgManager getInstance() {
if (instance == null) {
instance = new UnidbgManager();
}
return instance;
}
/**
* 初始化Unidbg环境
*/
public synchronized void initialize() {
if (initialized) {
LogUtils.d(TAG, "Unidbg已初始化,跳过");
return;
}
LogUtils.i(TAG, "开始初始化Unidbg环境...");
try {
// 1. 创建ARM64模拟器
LogUtils.d(TAG, "创建ARM64模拟器...");
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.dreamworld.app")
.build();
// 2. 设置库解析器
LogUtils.d(TAG, "设置库解析器...");
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
// 3. 创建DalvikVM
LogUtils.d(TAG, "创建DalvikVM...");
File apkFile = new File(AppConfig.APK_PATH);
vm = emulator.createDalvikVM(apkFile);
vm.setJni(this);
vm.setVerbose(false);
// 4. 加载依赖库
File libDir = new File(AppConfig.LIB_DIR);
LogUtils.d(TAG, "加载依赖库: " + AppConfig.CPP_SHARED_SO);
vm.loadLibrary(new File(libDir, AppConfig.CPP_SHARED_SO), false);
// 5. 加载目标库
LogUtils.d(TAG, "加载目标库: " + AppConfig.SECURITY_SO);
DalvikModule dm = vm.loadLibrary(new File(libDir, AppConfig.SECURITY_SO), false);
module = dm.getModule();
// 6. 调用JNI_OnLoad
LogUtils.d(TAG, "调用JNI_OnLoad...");
dm.callJNI_OnLoad(emulator);
// 7. 解析JNI类
LogUtils.d(TAG, "解析JNI类...");
jniWrapperClass = vm.resolveClass("com/dreamworld/secutil/JNIWrapper");
initialized = true;
LogUtils.success("Unidbg环境初始化成功");
} catch (Exception e) {
LogUtils.e(TAG, "Unidbg初始化失败", e);
throw new RuntimeException("Unidbg初始化失败", e);
}
}
/**
* 设置环境
*/
public void setEnvironment(int env) {
checkInitialized();
LogUtils.d(TAG, "setEnvironment(" + env + ")");
jniWrapperClass.callStaticJniMethod(
emulator,
METHOD_SET_ENV,
new StringObject(vm, String.valueOf(env))
);
}
/**
* 获取设备密钥ID
*/
public String getPriId() {
checkInitialized();
LogUtils.d(TAG, "getPriId()");
StringObject result = jniWrapperClass.callStaticJniMethodObject(
emulator,
METHOD_GET_PRI_ID
);
return result != null ? result.getValue() : null;
}
/**
* RSA签名
*/
public String rsaSign(String prefix, String data) {
checkInitialized();
LogUtils.d(TAG, "rsaSign()");
StringObject result = jniWrapperClass.callStaticJniMethodObject(
emulator,
METHOD_RSA_SIGN,
new StringObject(vm, prefix),
new StringObject(vm, data)
);
return result != null ? result.getValue() : null;
}
/**
* 解密密钥
*/
public String[] formatDK(String tempAesKey, String tempIv,
String aesKey, String hmacKey) {
checkInitialized();
LogUtils.d(TAG, "formatDK()");
DvmObject<?> result = jniWrapperClass.callStaticJniMethodObject(
emulator,
METHOD_FORMAT_DK,
new StringObject(vm, tempAesKey),
new StringObject(vm, tempIv),
new StringObject(vm, aesKey),
new StringObject(vm, hmacKey)
);
if (result instanceof ArrayObject) {
ArrayObject arrayObj = (ArrayObject) result;
DvmObject<?>[] elements = arrayObj.getValue();
if (elements != null && elements.length >= 2) {
String[] keys = new String[2];
keys[0] = ((StringObject) elements[0]).getValue();
keys[1] = ((StringObject) elements[1]).getValue();
return keys;
}
}
return null;
}
/**
* HMAC签名
*/
public String hmacSign(String hacKey, String stringToSign) {
checkInitialized();
LogUtils.d(TAG, "hmacSign()");
StringObject result = jniWrapperClass.callStaticJniMethodObject(
emulator,
METHOD_HMAC_SIGN,
new StringObject(vm, hacKey),
new StringObject(vm, stringToSign)
);
return result != null ? result.getValue() : null;
}
/**
* 检查是否已初始化
*/
private void checkInitialized() {
if (!initialized) {
throw new IllegalStateException("Unidbg未初始化,请先调用initialize()");
}
}
/**
* 销毁资源
*/
public synchronized void destroy() {
if (emulator != null) {
LogUtils.i(TAG, "销毁Unidbg资源...");
emulator.close();
emulator = null;
vm = null;
module = null;
jniWrapperClass = null;
initialized = false;
}
}
/**
* 是否已初始化
*/
public boolean isInitialized() {
return initialized;
}
}
7.2.5 密钥存储
package com.dreamworld.storage;
/**
* 密钥存储接口
*/
public interface KeyStorage {
void setDeviceId(String deviceId);
String getDeviceId();
void setKeyId(String keyId);
String getKeyId();
void setAesKey(String aesKey);
String getAesKey();
void setHacKey(String hacKey);
String getHacKey();
void setValidFrom(long validFrom);
long getValidFrom();
void setValidTo(long validTo);
long getValidTo();
boolean isKeyValid();
void clear();
}
package com.dreamworld.storage;
import com.dreamworld.config.AppConfig;
/**
* 内存密钥存储实现
*/
public class MemoryKeyStorage implements KeyStorage {
private String deviceId;
private String keyId;
private String aesKey;
private String hacKey;
private long validFrom;
private long validTo;
public MemoryKeyStorage() {
this.deviceId = AppConfig.DEVICE_ID;
}
@Override
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
@Override
public String getDeviceId() {
return deviceId;
}
@Override
public void setKeyId(String keyId) {
this.keyId = keyId;
}
@Override
public String getKeyId() {
return keyId;
}
@Override
public void setAesKey(String aesKey) {
this.aesKey = aesKey;
}
@Override
public String getAesKey() {
return aesKey;
}
@Override
public void setHacKey(String hacKey) {
this.hacKey = hacKey;
}
@Override
public String getHacKey() {
return hacKey;
}
@Override
public void setValidFrom(long validFrom) {
this.validFrom = validFrom;
}
@Override
public long getValidFrom() {
return validFrom;
}
@Override
public void setValidTo(long validTo) {
this.validTo = validTo;
}
@Override
public long getValidTo() {
return validTo;
}
@Override
public boolean isKeyValid() {
if (keyId == null || hacKey == null) {
return false;
}
long now = System.currentTimeMillis();
return now >= validFrom && now < validTo;
}
@Override
public void clear() {
keyId = null;
aesKey = null;
hacKey = null;
validFrom = 0;
validTo = 0;
}
}
7.3 业务层实现
7.3.1 数据模型
package com.dreamworld.model;
/**
* 激活请求模型
*/
public class ActivationRequest {
private String requestId;
private String deviceId;
private String deviceKeyId;
private String nonce;
private String timestamp;
private String sign;
public ActivationRequest(String requestId, String deviceId,
String deviceKeyId, String nonce, String timestamp, String sign) {
this.requestId = requestId;
this.deviceId = deviceId;
this.deviceKeyId = deviceKeyId;
this.nonce = nonce;
this.timestamp = timestamp;
this.sign = sign;
}
// Getters
public String getRequestId() { return requestId; }
public String getDeviceId() { return deviceId; }
public String getDeviceKeyId() { return deviceKeyId; }
public String getNonce() { return nonce; }
public String getTimestamp() { return timestamp; }
public String getSign() { return sign; }
}
package com.dreamworld.model;
/**
* 激活响应模型
*/
public class ActivationResponse {
private int code;
private boolean success;
private String msg;
private ActivationData data;
public int getCode() { return code; }
public boolean isSuccess() { return success; }
public String getMsg() { return msg; }
public ActivationData getData() { return data; }
public static class ActivationData {
private String requestId;
private KeySuite keySuite;
private String tempAesKey;
private String tempIv;
private String spPublicKey;
private String spPublicKeySign;
private String sign;
public String getRequestId() { return requestId; }
public KeySuite getKeySuite() { return keySuite; }
public String getTempAesKey() { return tempAesKey; }
public String getTempIv() { return tempIv; }
public String getSpPublicKey() { return spPublicKey; }
public String getSpPublicKeySign() { return spPublicKeySign; }
public String getSign() { return sign; }
}
public static class KeySuite {
private String keyId;
private KeySecret keySecret;
private long validFrom;
private long validTo;
public String getKeyId() { return keyId; }
public KeySecret getKeySecret() { return keySecret; }
public long getValidFrom() { return validFrom; }
public long getValidTo() { return validTo; }
}
public static class KeySecret {
private String aesKey;
private String hmacKey;
public String getAesKey() { return aesKey; }
public String getHmacKey() { return hmacKey; }
}
}
7.3.2 签名构造器
package com.dreamworld.security;
import com.dreamworld.config.AppConfig;
import com.dreamworld.utils.CryptoUtils;
/**
* 签名构造器
*/
public class SignatureBuilder {
/**
* 构造RSA签名载荷
* 格式: requestId:deviceId:deviceKeyId:nonce:timestamp
*/
public static String buildRsaSignPayload(String requestId, String deviceId,
String deviceKeyId, String nonce, String timestamp) {
return String.join(":", requestId, deviceId, deviceKeyId, nonce, timestamp);
}
/**
* 构造HMAC签名字符串
* 11个字段,每个字段后跟换行符
*/
public static String buildHmacSignString(String env, String version,
String keyId, String deviceId, String method, String accept,
String contentLanguage, String contentMd5, String contentType,
String timestamp, String nonce) {
StringBuilder sb = new StringBuilder();
sb.append(nullToEmpty(env)).append("\n");
sb.append(nullToEmpty(version)).append("\n");
sb.append(nullToEmpty(keyId)).append("\n");
sb.append(nullToEmpty(deviceId)).append("\n");
sb.append(nullToEmpty(method)).append("\n");
sb.append(nullToEmpty(accept)).append("\n");
sb.append(nullToEmpty(contentLanguage)).append("\n");
sb.append(nullToEmpty(contentMd5)).append("\n");
sb.append(nullToEmpty(contentType)).append("\n");
sb.append(nullToEmpty(timestamp)).append("\n");
sb.append(nullToEmpty(nonce)).append("\n");
return sb.toString();
}
/**
* 构造HMAC签名字符串(使用默认值)
*/
public static String buildHmacSignString(String keyId, String deviceId,
String method, String contentMd5, String timestamp, String nonce) {
return buildHmacSignString(
AppConfig.ENV,
AppConfig.APP_VERSION,
keyId,
deviceId,
method,
"application/json",
"zh-CN",
contentMd5,
"application/json",
timestamp,
nonce
);
}
private static String nullToEmpty(String str) {
return str != null ? str : "";
}
}
7.3.3 安全操作服务
package com.dreamworld.security;
import com.dreamworld.config.AppConfig;
import com.dreamworld.model.ActivationRequest;
import com.dreamworld.model.ActivationResponse;
import com.dreamworld.storage.KeyStorage;
import com.dreamworld.unidbg.UnidbgManager;
import com.dreamworld.utils.CryptoUtils;
import com.dreamworld.utils.LogUtils;
/**
* 安全操作服务
* 封装完整的安全激活流程
*/
public class SecurityOperation {
private static final String TAG = "SecurityOperation";
private final UnidbgManager unidbgManager;
private final KeyStorage keyStorage;
public SecurityOperation(UnidbgManager unidbgManager, KeyStorage keyStorage) {
this.unidbgManager = unidbgManager;
this.keyStorage = keyStorage;
}
/**
* 初始化安全环境
*/
public void initialize() {
LogUtils.i(TAG, "初始化安全环境...");
// 初始化Unidbg
unidbgManager.initialize();
// 设置生产环境(关键!)
unidbgManager.setEnvironment(1);
LogUtils.success("已设置生产环境");
}
/**
* 构造激活请求
*/
public ActivationRequest buildActivationRequest() {
LogUtils.i(TAG, "构造激活请求...");
// 获取设备ID
String deviceId = keyStorage.getDeviceId();
LogUtils.d(TAG, "设备ID: " + deviceId);
// 获取设备密钥ID
String priId = unidbgManager.getPriId();
LogUtils.d(TAG, "设备密钥ID: " + priId);
// 生成请求参数
String requestId = CryptoUtils.generateUUID();
String nonce = CryptoUtils.generateUUID();
String timestamp = CryptoUtils.generateTimestamp();
LogUtils.d(TAG, "请求ID: " + requestId);
LogUtils.d(TAG, "随机数: " + nonce);
LogUtils.d(TAG, "时间戳: " + timestamp);
// 构造签名载荷
String signPayload = SignatureBuilder.buildRsaSignPayload(
requestId, deviceId, priId, nonce, timestamp
);
LogUtils.d(TAG, "签名载荷: " + signPayload);
// RSA签名
String sign = unidbgManager.rsaSign("", signPayload);
LogUtils.d(TAG, "签名长度: " + (sign != null ? sign.length() : 0));
return new ActivationRequest(requestId, deviceId, priId, nonce, timestamp, sign);
}
/**
* 处理激活响应,解密密钥
*/
public boolean processActivationResponse(ActivationResponse response) {
LogUtils.i(TAG, "处理激活响应...");
if (response == null || !response.isSuccess()) {
LogUtils.fail("激活响应无效");
return false;
}
ActivationResponse.ActivationData data = response.getData();
if (data == null || data.getKeySuite() == null) {
LogUtils.fail("响应数据无效");
return false;
}
// 解密密钥
String[] decryptedKeys = unidbgManager.formatDK(
data.getTempAesKey(),
data.getTempIv(),
data.getKeySuite().getKeySecret().getAesKey(),
data.getKeySuite().getKeySecret().getHmacKey()
);
if (decryptedKeys == null || decryptedKeys.length < 2) {
LogUtils.fail("密钥解密失败");
return false;
}
// 保存密钥
keyStorage.setKeyId(data.getKeySuite().getKeyId());
keyStorage.setAesKey(decryptedKeys[0]);
keyStorage.setHacKey(decryptedKeys[1]);
keyStorage.setValidFrom(data.getKeySuite().getValidFrom());
keyStorage.setValidTo(data.getKeySuite().getValidTo());
LogUtils.success("密钥解密并保存成功");
LogUtils.d(TAG, "密钥ID: " + data.getKeySuite().getKeyId());
LogUtils.d(TAG, "AES密钥长度: " + decryptedKeys[0].length());
LogUtils.d(TAG, "HAC密钥长度: " + decryptedKeys[1].length());
// 计算有效期
long validDays = (data.getKeySuite().getValidTo() - data.getKeySuite().getValidFrom())
/ (1000 * 60 * 60 * 24);
LogUtils.d(TAG, "密钥有效期: " + validDays + "天");
return true;
}
/**
* 生成API签名
*/
public String generateApiSign(String method, String requestBody) {
String keyId = keyStorage.getKeyId();
String deviceId = keyStorage.getDeviceId();
String hacKey = keyStorage.getHacKey();
if (keyId == null || hacKey == null) {
LogUtils.e(TAG, "密钥未初始化");
return null;
}
String timestamp = CryptoUtils.generateTimestamp();
String nonce = CryptoUtils.generateUUIDWithDash();
String contentMd5 = CryptoUtils.md5Base64(requestBody != null ? requestBody : "");
String stringToSign = SignatureBuilder.buildHmacSignString(
keyId, deviceId, method, contentMd5, timestamp, nonce
);
return unidbgManager.hmacSign(hacKey, stringToSign);
}
/**
* 检查密钥是否有效
*/
public boolean isKeyValid() {
return keyStorage.isKeyValid();
}
/**
* 清理资源
*/
public void cleanup() {
LogUtils.i(TAG, "清理安全资源...");
unidbgManager.destroy();
keyStorage.clear();
}
}
7.3.4 API客户端
package com.dreamworld.network;
import com.dreamworld.config.AppConfig;
import com.dreamworld.model.ActivationRequest;
import com.dreamworld.model.ActivationResponse;
import com.dreamworld.storage.KeyStorage;
import com.dreamworld.unidbg.UnidbgManager;
import com.dreamworld.utils.CryptoUtils;
import com.dreamworld.utils.LogUtils;
import com.dreamworld.security.SignatureBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* 密钥套件API客户端
*/
public class KeySuiteApiClient {
private static final String TAG = "KeySuiteApiClient";
private final Gson gson;
public KeySuiteApiClient() {
this.gson = new GsonBuilder().setPrettyPrinting().create();
}
/**
* 发送激活请求
*/
public ActivationResponse activate(ActivationRequest request) {
LogUtils.i(TAG, "发送激活请求...");
String url = AppConfig.API_HOST + AppConfig.KEY_SUITE_PATH;
String jsonBody = gson.toJson(request);
LogUtils.d(TAG, "URL: " + url);
LogUtils.d(TAG, "请求体: " + jsonBody);
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(AppConfig.CONNECT_TIMEOUT);
conn.setReadTimeout(AppConfig.READ_TIMEOUT);
conn.setDoOutput(true);
// 设置请求头
String timestamp = CryptoUtils.generateTimestamp();
String nonce = CryptoUtils.generateUUID();
String contentMd5 = CryptoUtils.md5Base64(jsonBody);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("X-DW-Deviceid", request.getDeviceId());
conn.setRequestProperty("X-DW-DeviceType", AppConfig.DEVICE_TYPE);
conn.setRequestProperty("X-DW-ModelName", AppConfig.MODEL_NAME);
conn.setRequestProperty("X-DW-DeviceModel", AppConfig.DEVICE_MODEL);
conn.setRequestProperty("X-DW-APP-Version", AppConfig.APP_VERSION);
conn.setRequestProperty("X-DW-Version", "1.0");
conn.setRequestProperty("X-DW-Env", AppConfig.ENV);
conn.setRequestProperty("X-DW-Timestamp", timestamp);
conn.setRequestProperty("X-DW-Nonce", nonce);
conn.setRequestProperty("Content-MD5", contentMd5);
conn.setRequestProperty("Content-Language", "zh-CN");
// 发送请求体
try (OutputStream os = conn.getOutputStream()) {
os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
}
// 读取响应
int responseCode = conn.getResponseCode();
LogUtils.d(TAG, "HTTP响应码: " + responseCode);
String responseBody = readResponse(conn);
LogUtils.d(TAG, "响应体: " + responseBody);
return gson.fromJson(responseBody, ActivationResponse.class);
} catch (Exception e) {
LogUtils.e(TAG, "请求失败", e);
return null;
}
}
private String readResponse(HttpURLConnection conn) throws IOException {
InputStream is;
try {
is = conn.getInputStream();
} catch (IOException e) {
is = conn.getErrorStream();
}
if (is == null) {
return "";
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
}
}
package com.dreamworld.network;
import com.dreamworld.config.AppConfig;
import com.dreamworld.storage.KeyStorage;
import com.dreamworld.unidbg.UnidbgManager;
import com.dreamworld.utils.CryptoUtils;
import com.dreamworld.utils.LogUtils;
import com.dreamworld.security.SignatureBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* 业务API客户端
*/
public class BusinessApiClient {
private static final String TAG = "BusinessApiClient";
private final KeyStorage keyStorage;
private final UnidbgManager unidbgManager;
private final Gson gson;
public BusinessApiClient(KeyStorage keyStorage, UnidbgManager unidbgManager) {
this.keyStorage = keyStorage;
this.unidbgManager = unidbgManager;
this.gson = new GsonBuilder().setPrettyPrinting().create();
}
/**
* 发送GET请求
*/
public ApiResponse get(String path) {
return request("GET", path, null);
}
/**
* 发送POST请求
*/
public ApiResponse post(String path, String body) {
return request("POST", path, body);
}
/**
* 发送请求
*/
private ApiResponse request(String method, String path, String body) {
LogUtils.i(TAG, "发送" + method + "请求: " + path);
String url = AppConfig.API_HOST + path;
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod(method);
conn.setConnectTimeout(AppConfig.CONNECT_TIMEOUT);
conn.setReadTimeout(AppConfig.READ_TIMEOUT);
// 生成签名参数
String deviceId = keyStorage.getDeviceId();
String keyId = keyStorage.getKeyId();
String hacKey = keyStorage.getHacKey();
String timestamp = CryptoUtils.generateTimestamp();
String nonce = CryptoUtils.generateUUIDWithDash();
String contentMd5 = CryptoUtils.md5Base64(body != null ? body : "");
// 构造签名字符串
String stringToSign = SignatureBuilder.buildHmacSignString(
keyId, deviceId, method, contentMd5, timestamp, nonce
);
// HMAC签名
String sign = unidbgManager.hmacSign(hacKey, stringToSign);
// 设置请求头
conn.setRequestProperty("X-DW-Deviceid", deviceId);
conn.setRequestProperty("X-DW-APP-Version", AppConfig.APP_VERSION);
conn.setRequestProperty("X-DW-Env", AppConfig.ENV);
conn.setRequestProperty("X-DW-Version", AppConfig.APP_VERSION);
conn.setRequestProperty("X-DW-Key", keyId);
conn.setRequestProperty("X-DW-Timestamp", timestamp);
conn.setRequestProperty("X-DW-Nonce", nonce);
conn.setRequestProperty("X-DW-Sign", sign);
conn.setRequestProperty("X-DW-Token", "");
conn.setRequestProperty("X-DW-DeviceType", AppConfig.DEVICE_TYPE);
conn.setRequestProperty("X-DW-ModelName", AppConfig.MODEL_NAME);
conn.setRequestProperty("X-DW-DeviceModel", AppConfig.DEVICE_MODEL);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Content-Language", "zh-CN");
conn.setRequestProperty("Content-MD5", contentMd5);
conn.setRequestProperty("Accept", "application/json");
// 发送请求体(如果有)
if (body != null && !body.isEmpty()) {
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}
}
// 读取响应
int responseCode = conn.getResponseCode();
String responseBody = readResponse(conn);
LogUtils.d(TAG, "HTTP响应码: " + responseCode);
LogUtils.d(TAG, "响应体长度: " + responseBody.length());
ApiResponse response = new ApiResponse();
response.setHttpCode(responseCode);
response.setRawResponse(responseBody);
// 解析JSON
try {
JsonObject json = gson.fromJson(responseBody, JsonObject.class);
if (json != null) {
response.setCode(json.has("code") ? json.get("code").getAsInt() : 0);
response.setSuccess(json.has("success") && json.get("success").getAsBoolean());
response.setMsg(json.has("msg") ? json.get("msg").getAsString() : "");
}
} catch (Exception e) {
LogUtils.w(TAG, "JSON解析失败: " + e.getMessage());
}
return response;
} catch (Exception e) {
LogUtils.e(TAG, "请求失败", e);
ApiResponse errorResponse = new ApiResponse();
errorResponse.setHttpCode(-1);
errorResponse.setMsg(e.getMessage());
return errorResponse;
}
}
private String readResponse(HttpURLConnection conn) throws IOException {
InputStream is;
try {
is = conn.getInputStream();
} catch (IOException e) {
is = conn.getErrorStream();
}
if (is == null) {
return "";
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(is, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
}
}
/**
* API响应
*/
public static class ApiResponse {
private int httpCode;
private int code;
private boolean success;
private String msg;
private String rawResponse;
public int getHttpCode() { return httpCode; }
public void setHttpCode(int httpCode) { this.httpCode = httpCode; }
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public String getRawResponse() { return rawResponse; }
public void setRawResponse(String rawResponse) { this.rawResponse = rawResponse; }
}
}
7.4 应用层实现
7.4.1 主程序入口
package com.dreamworld;
import com.dreamworld.config.AppConfig;
import com.dreamworld.model.ActivationRequest;
import com.dreamworld.model.ActivationResponse;
import com.dreamworld.network.BusinessApiClient;
import com.dreamworld.network.KeySuiteApiClient;
import com.dreamworld.security.SecurityOperation;
import com.dreamworld.storage.KeyStorage;
import com.dreamworld.storage.MemoryKeyStorage;
import com.dreamworld.unidbg.UnidbgManager;
import com.dreamworld.utils.LogUtils;
import java.io.FileWriter;
import java.io.IOException;
/**
* 主程序入口
*/
public class Main {
private static final String TAG = "Main";
public static void main(String[] args) {
printBanner();
// 验证配置
if (!AppConfig.validate()) {
LogUtils.fail("配置验证失败,请检查资源文件");
return;
}
// 初始化组件
UnidbgManager unidbgManager = UnidbgManager.getInstance();
KeyStorage keyStorage = new MemoryKeyStorage();
SecurityOperation securityOp = new SecurityOperation(unidbgManager, keyStorage);
try {
// 执行完整流程
runSecurityChain(securityOp, keyStorage, unidbgManager);
} catch (Exception e) {
LogUtils.e(TAG, "程序执行出错", e);
} finally {
// 清理资源
securityOp.cleanup();
}
printFooter();
}
/**
* 执行完整的安全调用链
*/
private static void runSecurityChain(SecurityOperation securityOp,
KeyStorage keyStorage, UnidbgManager unidbgManager) {
// 步骤1:初始化安全环境
LogUtils.separator("步骤1:初始化安全环境");
securityOp.initialize();
// 步骤2:构造激活请求
LogUtils.separator("步骤2:构造激活请求");
ActivationRequest request = securityOp.buildActivationRequest();
if (request == null) {
LogUtils.fail("构造激活请求失败");
return;
}
LogUtils.success("激活请求构造完成");
printRequestInfo(request);
// 步骤3:发送激活请求
LogUtils.separator("步骤3:发送激活请求");
KeySuiteApiClient keySuiteClient = new KeySuiteApiClient();
ActivationResponse response = keySuiteClient.activate(request);
if (response == null) {
LogUtils.fail("激活请求发送失败");
return;
}
if (response.getCode() != 0) {
LogUtils.fail("激活失败: " + response.getMsg() + " (code=" + response.getCode() + ")");
printErrorSuggestion(response.getCode());
return;
}
LogUtils.success("激活请求成功");
// 步骤4:处理响应,解密密钥
LogUtils.separator("步骤4:解密密钥");
boolean decryptSuccess = securityOp.processActivationResponse(response);
if (!decryptSuccess) {
LogUtils.fail("密钥解密失败");
return;
}
// 步骤5:调用业务API
LogUtils.separator("步骤5:调用业务API");
BusinessApiClient businessClient = new BusinessApiClient(keyStorage, unidbgManager);
// 示例:获取商品列表
BusinessApiClient.ApiResponse apiResponse = businessClient.get(
"/api/v1/products/list?page=1&size=10"
);
if (apiResponse != null && apiResponse.isSuccess()) {
LogUtils.success("业务API调用成功!");
LogUtils.i(TAG, "响应数据长度: " + apiResponse.getRawResponse().length());
// 保存数据到文件
saveToFile(apiResponse.getRawResponse(), "output.json");
LogUtils.success("数据已保存到 output.json");
} else {
LogUtils.fail("业务API调用失败");
if (apiResponse != null) {
LogUtils.e(TAG, "错误码: " + apiResponse.getCode());
LogUtils.e(TAG, "错误信息: " + apiResponse.getMsg());
}
}
}
/**
* 打印请求信息
*/
private static void printRequestInfo(ActivationRequest request) {
LogUtils.d(TAG, "请求ID: " + request.getRequestId());
LogUtils.d(TAG, "设备ID: " + request.getDeviceId());
LogUtils.d(TAG, "设备密钥ID: " + request.getDeviceKeyId());
LogUtils.d(TAG, "时间戳: " + request.getTimestamp());
LogUtils.d(TAG, "签名长度: " + request.getSign().length());
}
/**
* 打印错误建议
*/
private static void printErrorSuggestion(int errorCode) {
String suggestion;
switch (errorCode) {
case 144011:
suggestion = "请检查是否正确设置了生产环境 (setEnvironment(1))";
break;
case 144012:
suggestion = "请检查签名算法和签名载荷格式";
break;
case 144013:
suggestion = "请检查系统时间是否正确";
break;
case 100002:
suggestion = "请检查是否发送了所有必需的Header";
break;
case 100022:
suggestion = "请检查Content-MD5是否使用Base64编码";
break;
default:
suggestion = "请查看详细日志分析问题";
}
LogUtils.i(TAG, "建议: " + suggestion);
}
/**
* 保存数据到文件
*/
private static void saveToFile(String content, String filename) {
try (FileWriter writer = new FileWriter(filename)) {
writer.write(content);
} catch (IOException e) {
LogUtils.e(TAG, "保存文件失败", e);
}
}
/**
* 打印Banner
*/
private static void printBanner() {
System.out.println();
System.out.println("╔════════════════════════════════════════════════════════════╗");
System.out.println("║ ║");
System.out.println("║ 梦想世界APP安全调用链演示程序 ║");
System.out.println("║ ║");
System.out.println("║ 版本: 1.0.0 ║");
System.out.println("║ 声明: 仅供安全研究和技术学习使用 ║");
System.out.println("║ ║");
System.out.println("╚════════════════════════════════════════════════════════════╝");
System.out.println();
}
/**
* 打印Footer
*/
private static void printFooter() {
System.out.println();
System.out.println("╔════════════════════════════════════════════════════════════╗");
System.out.println("║ 程序执行完成 ║");
System.out.println("╚════════════════════════════════════════════════════════════╝");
System.out.println();
}
}
7.5 运行与测试
7.5.1 编译运行
# 进入项目目录
cd dreamworld-security-chain
# 编译项目
mvn clean compile
# 运行程序
mvn exec:java -Dexec.mainClass="com.dreamworld.Main" -q
7.5.2 预期输出
╔════════════════════════════════════════════════════════════╗
║ ║
║ 梦想世界APP安全调用链演示程序 ║
║ ║
║ 版本: 1.0.0 ║
║ 声明: 仅供安全研究和技术学习使用 ║
║ ║
╚════════════════════════════════════════════════════════════╝
═══════════════════════════════════════════════════════════
步骤1:初始化安全环境
═══════════════════════════════════════════════════════════
[2026-01-09 10:30:00.123] [INFO] [UnidbgManager] 开始初始化Unidbg环境...
[2026-01-09 10:30:01.456] [DEBUG] [UnidbgManager] 创建ARM64模拟器...
[2026-01-09 10:30:02.789] [DEBUG] [UnidbgManager] 加载目标库: libSecurityCore.so
✓ Unidbg环境初始化成功
✓ 已设置生产环境
═══════════════════════════════════════════════════════════
步骤2:构造激活请求
═══════════════════════════════════════════════════════════
[2026-01-09 10:30:03.012] [INFO] [SecurityOperation] 构造激活请求...
[2026-01-09 10:30:03.123] [DEBUG] [SecurityOperation] 设备ID: a1b2c3d4e5f67890
[2026-01-09 10:30:03.234] [DEBUG] [SecurityOperation] 设备密钥ID: f1e2d3c4b5a6978869574a3b2c1d0e0f
✓ 激活请求构造完成
═══════════════════════════════════════════════════════════
步骤3:发送激活请求
═══════════════════════════════════════════════════════════
[2026-01-09 10:30:03.345] [INFO] [KeySuiteApiClient] 发送激活请求...
[2026-01-09 10:30:04.567] [DEBUG] [KeySuiteApiClient] HTTP响应码: 200
✓ 激活请求成功
═══════════════════════════════════════════════════════════
步骤4:解密密钥
═══════════════════════════════════════════════════════════
[2026-01-09 10:30:04.678] [INFO] [SecurityOperation] 处理激活响应...
[2026-01-09 10:30:04.789] [DEBUG] [SecurityOperation] 密钥ID: key1a2b3c4d5e6f7a8b9c0d1e2f3a4b5
[2026-01-09 10:30:04.890] [DEBUG] [SecurityOperation] AES密钥长度: 44
[2026-01-09 10:30:04.901] [DEBUG] [SecurityOperation] HAC密钥长度: 44
✓ 密钥解密并保存成功
═══════════════════════════════════════════════════════════
步骤5:调用业务API
═══════════════════════════════════════════════════════════
[2026-01-09 10:30:05.012] [INFO] [BusinessApiClient] 发送GET请求: /api/v1/products/list
[2026-01-09 10:30:05.789] [DEBUG] [BusinessApiClient] HTTP响应码: 200
[2026-01-09 10:30:05.890] [DEBUG] [BusinessApiClient] 响应体长度: 85234
✓ 业务API调用成功!
[2026-01-09 10:30:05.901] [INFO] [Main] 响应数据长度: 85234
✓ 数据已保存到 output.json
╔════════════════════════════════════════════════════════════╗
║ 程序执行完成 ║
╚════════════════════════════════════════════════════════════╝
7.5.3 单元测试
package com.dreamworld;
import com.dreamworld.security.SignatureBuilder;
import com.dreamworld.utils.CryptoUtils;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* 安全模块单元测试
*/
public class SecurityTest {
@Test
public void testMd5Base64() {
// 空字符串的MD5 (Base64)
String result = CryptoUtils.md5Base64("");
assertEquals("1B2M2Y8AsgTpgAmY7PhCfg==", result);
}
@Test
public void testMd5Hex() {
// 空字符串的MD5 (十六进制)
String result = CryptoUtils.md5Hex("");
assertEquals("d41d8cd98f00b204e9800998ecf8427e", result);
}
@Test
public void testRsaSignPayload() {
String payload = SignatureBuilder.buildRsaSignPayload(
"requestId", "deviceId", "deviceKeyId", "nonce", "timestamp"
);
assertEquals("requestId:deviceId:deviceKeyId:nonce:timestamp", payload);
}
@Test
public void testHmacSignString() {
String signString = SignatureBuilder.buildHmacSignString(
"prod", "8.x.x", "keyId", "deviceId", "GET",
"application/json", "zh-CN", "md5", "application/json",
"timestamp", "nonce"
);
// 验证格式:11个字段,每个后面有换行符
String[] lines = signString.split("\n", -1);
assertEquals(12, lines.length); // 11个字段 + 最后一个空字符串
assertEquals("prod", lines[0]);
assertEquals("8.x.x", lines[1]);
assertEquals("keyId", lines[2]);
assertEquals("deviceId", lines[3]);
assertEquals("GET", lines[4]);
assertEquals("application/json", lines[5]);
assertEquals("zh-CN", lines[6]);
assertEquals("md5", lines[7]);
assertEquals("application/json", lines[8]);
assertEquals("timestamp", lines[9]);
assertEquals("nonce", lines[10]);
assertEquals("", lines[11]); // 最后一个换行符后的空字符串
}
@Test
public void testGenerateUUID() {
String uuid = CryptoUtils.generateUUID();
assertNotNull(uuid);
assertEquals(32, uuid.length());
assertFalse(uuid.contains("-"));
}
@Test
public void testGenerateTimestamp() {
String timestamp = CryptoUtils.generateTimestamp();
assertNotNull(timestamp);
assertTrue(timestamp.length() >= 13); // 毫秒级时间戳
long ts = Long.parseLong(timestamp);
long now = System.currentTimeMillis();
assertTrue(Math.abs(ts - now) < 1000); // 误差小于1秒
}
}
7.6 错误处理与重试
7.6.1 错误处理策略
package com.dreamworld.utils;
/**
* 错误处理工具
*/
public class ErrorHandler {
/**
* API错误码枚举
*/
public enum ApiError {
SUCCESS(0, "成功", null),
DEVICE_KEY_NOT_FOUND(144011, "设备密钥未找到", "请检查环境设置"),
SIGNATURE_INVALID(144012, "签名验证失败", "请检查签名算法"),
TIMESTAMP_EXPIRED(144013, "时间戳过期", "请检查系统时间"),
MISSING_PARAMETER(100002, "缺少必要参数", "请检查请求头"),
CONTENT_MD5_INVALID(100022, "Content-MD5验证失败", "请使用Base64编码"),
VERIFY_KEY_ERROR(144001, "验证密钥错误", "请使用key-suite路径"),
UNKNOWN(-1, "未知错误", "请查看详细日志");
private final int code;
private final String message;
private final String suggestion;
ApiError(int code, String message, String suggestion) {
this.code = code;
this.message = message;
this.suggestion = suggestion;
}
public static ApiError fromCode(int code) {
for (ApiError error : values()) {
if (error.code == code) {
return error;
}
}
return UNKNOWN;
}
public int getCode() { return code; }
public String getMessage() { return message; }
public String getSuggestion() { return suggestion; }
}
/**
* 处理API错误
*/
public static void handleApiError(int code, String msg) {
ApiError error = ApiError.fromCode(code);
LogUtils.e("ErrorHandler", "API错误: " + error.getMessage());
LogUtils.e("ErrorHandler", "错误码: " + code);
LogUtils.e("ErrorHandler", "服务器消息: " + msg);
if (error.getSuggestion() != null) {
LogUtils.i("ErrorHandler", "建议: " + error.getSuggestion());
}
}
/**
* 判断是否可重试
*/
public static boolean isRetryable(int code) {
switch (code) {
case 144013: // 时间戳过期 - 可重试
case -1: // 网络错误 - 可重试
return true;
default:
return false;
}
}
}
7.6.2 重试机制
package com.dreamworld.utils;
import java.util.function.Supplier;
/**
* 重试工具
*/
public class RetryUtils {
private static final String TAG = "RetryUtils";
/**
* 带重试的执行
*
* @param action 要执行的操作
* @param maxRetries 最大重试次数
* @param retryDelayMs 重试间隔(毫秒)
* @return 执行结果
*/
public static <T> T executeWithRetry(Supplier<T> action, int maxRetries, long retryDelayMs) {
int attempt = 0;
Exception lastException = null;
while (attempt <= maxRetries) {
try {
if (attempt > 0) {
LogUtils.i(TAG, "第" + attempt + "次重试...");
Thread.sleep(retryDelayMs);
}
return action.get();
} catch (Exception e) {
lastException = e;
LogUtils.w(TAG, "执行失败: " + e.getMessage());
attempt++;
}
}
LogUtils.e(TAG, "重试" + maxRetries + "次后仍然失败");
throw new RuntimeException("操作失败", lastException);
}
/**
* 带条件重试的执行
*/
public static <T> T executeWithRetry(Supplier<T> action,
java.util.function.Predicate<T> shouldRetry,
int maxRetries, long retryDelayMs) {
int attempt = 0;
T result = null;
while (attempt <= maxRetries) {
try {
if (attempt > 0) {
LogUtils.i(TAG, "第" + attempt + "次重试...");
Thread.sleep(retryDelayMs);
}
result = action.get();
if (!shouldRetry.test(result)) {
return result;
}
LogUtils.w(TAG, "结果不满足条件,准备重试");
attempt++;
} catch (Exception e) {
LogUtils.w(TAG, "执行异常: " + e.getMessage());
attempt++;
}
}
LogUtils.w(TAG, "重试" + maxRetries + "次后返回最后结果");
return result;
}
}
7.6.3 使用示例
// 带重试的激活请求
ActivationResponse response = RetryUtils.executeWithRetry(
() -> keySuiteClient.activate(request),
result -> result == null || ErrorHandler.isRetryable(result.getCode()),
3, // 最大重试3次
1000 // 重试间隔1秒
);
7.7 本章小结
7.7.1 项目结构回顾
┌─────────────────────────────────────────────────────────────────┐
│ 项目架构总结 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用层 │
│ ├── Main.java 主程序入口 │
│ └── 负责流程编排和用户交互 │
│ │
│ 业务层 │
│ ├── SecurityOperation 安全操作服务 │
│ ├── SignatureBuilder 签名构造器 │
│ ├── KeySuiteApiClient 密钥套件API │
│ └── BusinessApiClient 业务API │
│ │
│ 基础层 │
│ ├── UnidbgManager Unidbg管理器 │
│ ├── KeyStorage 密钥存储 │
│ ├── CryptoUtils 加密工具 │
│ └── LogUtils 日志工具 │
│ │
└─────────────────────────────────────────────────────────────────┘
7.7.2 关键实现要点
- 单例模式管理Unidbg:避免重复初始化,节省资源
- 分层架构:职责清晰,便于维护和测试
- 错误处理:完善的错误码处理和重试机制
- 配置管理:集中管理配置,便于修改
7.7.3 下一章预告
在下一章中,我们将进入工程化与生产阶段:
- 从原型到生产系统的转变
- 性能优化策略
- 监控和告警
- 部署和运维
本章附录
附录A:完整的pom.xml
(见7.1.3节)
附录B:项目目录结构
(见7.1.2节)
附录C:常用命令
# 编译
mvn clean compile
# 运行
mvn exec:java -Dexec.mainClass="com.dreamworld.Main" -q
# 测试
mvn test
# 打包
mvn package
# 运行打包后的jar
java -jar target/security-chain-1.0.0.jar
本章完