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

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

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

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

当攻击者拿到你的 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 反复使用

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

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

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

最终你会发现:

安全只是起点,

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

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

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

相关推荐
xixixi7777720 分钟前
量子通信是当前信息安全和通信领域最前沿、最具变革性的技术之一
安全·信息安全·量子计算·通信·量子通信·密钥·传输
WLJT12312312332 分钟前
守护自然与滋养民生的绿色之路
大数据·安全
C++ 老炮儿的技术栈1 小时前
什么是通信规约
开发语言·数据结构·c++·windows·算法·安全·链表
stevenzqzq1 小时前
android启动初始化和注入理解3
android
五仁火烧2 小时前
生产环境中配置了接口3000后,不能启动,改成8080后就可以
linux·网络·安全·vue
专业开发者2 小时前
借助安全返场方案提升智慧建筑能效的新机遇
物联网·安全
菩提小狗2 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
城东米粉儿3 小时前
compose 状态提升 笔记
android
●VON3 小时前
跨模态暗流:多模态安全攻防全景解析
人工智能·学习·安全·von
粤M温同学4 小时前
Android 实现沉浸式状态栏
android