如何防防防之防抓包伪造请求

通常,我们讨论的话题都是如何写出强大的功能。

但今天,咱们换个视角,聊点不一样的------
应用安全:防防防,反反反。

防,不是防用户,而是防"逆向视角下的你自己"。

当攻击者拿到你的 APK / IPA / 可执行文件时,

TA 不关心你的业务多优雅、架构多清晰,

TA 只关心三件事:

  • 能不能看懂
  • 能不能改
  • 能不能利用,搞钱

所谓"防",本质上就是:
让逆向工程变得更慢、更贵、更不确定。

防静态分析、防动态注入、防抓包、防内存Dump、防SO替换、防Inline Hook、防PLT/GOT劫持、防Java Hook、防Native Hook、防Xposed、防Substrate、防Frida、防Magisk模块、防VirtualXposed、防LSPosed、反调试、反单步、反附加、反Trace、反调试器伪装、反ROOT、 反解锁、反系统篡改、反环境伪造、防重打包、防二次签名、防资源替换、防DEX注入、防脚本、 防自动化、防模拟器、防云手机、防小三,没有啦,后面这个是我刻意搞怪😏加上去的。

RSA + AES 加密

防逆向工程的第一步,往往不是混淆、不是反调试,而是加密

因为只要数据是明文,所有防护最终都会变成"延迟被看懂的时间"。

在密码学的江湖里,最常被拎出来的两位老前辈,非 RSA 非对称加密AES 对称加密 莫属。

一个安全、稳重、名门正派,但出手慢;

一个手快、效率高、杀伤力强,但最大的软肋就是------密钥怎么安全地交出去

所以现实世界里,从来不是二选一。
小孩子才做选择,而你,全都要。

你不慌不忙,先在客户端本地生成了一把随机的 AES Key

这把 key 很短命,只服务当前这一次通信,用完就丢,连自己都不打算再认。

你用它把真正的业务数据------资源、配置、秘密、惊喜------统统加密成一坨谁也看不懂的密文。

但你心里很清楚:
AES 再快,也怕 key 泄露。

于是你转身请出了 RSA。

你拿起对象早就公开在客户端里的 RSA 公钥

把刚才那把 AES Key 加密一层。

现在好了:

  • 数据,被 AES 锁住
  • AES 的钥匙,又被 RSA 锁住

双保险。

你把这两样东西------
RSA 加密过的 AES Key 和 AES 加密后的密文

一并发了出去。

此时,无论是抓包、代理、小三、还是半路劫持的人,

看到的都只是两坨"看不懂,也用不了"的东西。

另一头,你女朋友收到了这份"神秘快递"。

她当然知道怎么玩这套。

她先拿出自己珍藏、从不外泄的 RSA 私钥

轻轻一解,

RSA 的包装就被拆开,真正的 AES Key 映入眼帘。

然后,她用这把 key,

把那段密文慢慢解开。

数据复原,信息重现,惊喜如约而至。

kotlin 复制代码
package site.doramusic.app.http

import dora.util.CryptoUtils
import java.security.SecureRandom

object SecureRequestBuilder {

    const val AES_KEY_LENGTH = 16 // bytes (128位)
    const val RSA_PUBLIC = ""   // 等待公钥...

    enum class SecureMode {
        NONE,
        ENC,
        ENC_SIGN
    }

    /**
     * 获取随机key。
     */
    @JvmStatic
    fun getRandomKey(): String {
        val sb = StringBuilder(AES_KEY_LENGTH)
        val random = SecureRandom()
        repeat(AES_KEY_LENGTH) {
            when (random.nextInt(3)) {
                0 -> {
                    // 0-9
                    sb.append(random.nextInt(10))
                }
                1 -> {
                    // A-Z
                    sb.append((random.nextInt(26) + 'A'.code).toChar())
                }
                2 -> {
                    // a-z
                    sb.append((random.nextInt(26) + 'a'.code).toChar())
                }
            }
        }
        return sb.toString()
    }

    @JvmStatic
    fun build(
        req: BaseReq,
        mode: SecureMode
    ): ReqBody? {
        return when (mode) {
            // 明文
            SecureMode.NONE -> {
                ReqBody(
                    mode = "NONE",
                    data = req.payload
                )
            }
            // 端到端加密
            SecureMode.ENC -> {
                val aesKey = getRandomKey()
                ReqBody(
                    mode = "ENC",
                    key = CryptoUtils.encryptByPublic(RSA_PUBLIC, aesKey),
                    data = CryptoUtils.encryptAES(aesKey, req.payload)
                )
            }
            // 端到端加密 + 客户端签名
            SecureMode.ENC_SIGN -> {
                // 不告诉你,这个项目不提供可信客户端能力
                null
            }
        }
    }
}

使用SecureRandom生成完全随机的key。

kotlin 复制代码
package site.doramusic.app.http

import com.google.gson.Gson
import dora.util.GlobalContext
import dora.util.LanguageUtils
import java.lang.reflect.Modifier
import java.util.Locale

abstract class BaseReq {

    /**
     * 根据不同的语种返回本地化的内容。
     */
    var lang: String = ""

    /**
     * 数据载体。
     */
    var payload: String = ""

    /**
     * 防抓包伪造签名重复请求,签名过期,拒绝请求。
     */
    var timestamp: String = ""

    /**
     * 可信客户端签名,ENC_SIGN模式下,签名不正确,拒绝请求。
     */
    var signature: String? = null

    init {
        lang = LanguageUtils.getLangTag(GlobalContext.get()).ifEmpty { Locale.getDefault().language }
        timestamp = (System.currentTimeMillis() / 1000).toString()
    }

    /**
     * 对数据进行排序,保证唯一性,返回排序后的JSON字符串。
     */
    fun sort(): String {
        val map = sortedMapOf<String, Any?>()
        var clazz: Class<*>? = this.javaClass
        while (clazz != null && clazz != BaseReq::class.java) {
            clazz.declaredFields
                .filter { field ->
                    !field.isSynthetic &&
                            !Modifier.isStatic(field.modifiers)
                }
                .forEach { field ->
                    field.isAccessible = true
                    map[field.name] = field[this]
                }
            clazz = clazz.superclass
        }
        // 父类字段(显式加入,避免遗漏)
        map["lang"] = lang
        map["payload"] = payload
        map["timestamp"] = timestamp
        return Gson().toJson(map)
    }
}

大致思路如上,如需查看完整实现与工程细节,可直接进入这个传送门:
github.com/dora4/DoraM...

复用

能提供无限自由组合的复用能力 ,是大工程得以长期演进的基石。

复用不是复制粘贴,也不是简单封装几个工具类,而是结构层面的能力释放

抽象与提炼的水平,决定了一套工程的成熟度。

你抽的是"功能",还是"能力";

你复用的是"代码",还是"模型";

这些选择,会在项目规模放大之后,给出完全不同的回报。

当架构设计足够清晰、边界足够稳定时,

你甚至可以做到------
一份后端代码,对接 n 个承载同一品牌理念的 App。

你只需要把真正"通用"的东西抽出来:

  • 建议与反馈
  • FAQ / 帮助中心
  • App 版本分发与灰度控制
  • 首页横幅广告
  • 首页直播间 / 内容推荐
  • 配置信息与功能开关
  • 系统通知
  • 促销活动
  • 埋点统计与用户行为分析

这些模块,只写一次,

却可以被全品牌、全产品线、全形态的 App 反复使用

而客户端,只负责表现、体验和差异化。

真正复杂、真正需要稳定演进的部分,

被牢牢收敛在可控的架构之中。

最终你会发现:

安全只是起点,

加密只是手段,
架构与复用,才是工程走得远的根本原因。

工程不是写完一次就结束,

而是要经得起规模、时间和变化的反复考验。

相关推荐
2501_915909063 小时前
iOS 项目中常被忽略的 Bundle ID 管理问题
android·ios·小程序·https·uni-app·iphone·webview
2501_915921433 小时前
iOS App 测试的工程化实践,多工具协同的一些尝试
android·ios·小程序·https·uni-app·iphone·webview
爱埋珊瑚海~~4 小时前
Android Studio模拟器一直加载中
android·ide·android studio
C+++Python4 小时前
PHP 反射 API
android·java·php
G31135422734 小时前
android之IM即时通信原理
android
恒星科通4 小时前
隧道高清晰广播系统,破解隧道声学难题 为司乘安全加码
人工智能·安全
编程大师哥4 小时前
Android Studio 2025 从性能优化到开发体验下载安装教程安装包
android·ide·android studio
我又来搬代码了4 小时前
【Android】【Compose】Compose知识点复习(二)
android
真上帝的左手4 小时前
7. 网络安全-专栏说明
安全·web安全