第七章:完整调用链实现

26 阅读20分钟

第七章:完整调用链实现

本章字数:约30000字 阅读时间:约100分钟 难度等级:★★★★☆

声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理。文中的"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。


引言

经过前面六章的铺垫,我们已经:

  1. 了解了梦想世界APP的安全防护体系
  2. 掌握了Unidbg的使用方法
  3. 剖析了完整的JNI调用链
  4. 攻克了三大核心坑点

现在,是时候把所有组件整合起来,实现一个完整的、可运行的调用链了。

本章将从零开始,一步步构建完整的项目,最终实现:

  • 自动化的安全激活流程
  • 可靠的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 关键实现要点

  1. 单例模式管理Unidbg:避免重复初始化,节省资源
  2. 分层架构:职责清晰,便于维护和测试
  3. 错误处理:完善的错误码处理和重试机制
  4. 配置管理:集中管理配置,便于修改

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

本章完