百趣云 百趣云的博客

RSA 在 App 加密中的应用:公钥定位与分段加密

RSA 在 App 里的典型角色

App 里 RSA 几乎不做主加密(太慢),而是两个固定角色:

  1. 密钥交换:AES 密钥用 RSA 加密传输(网易云 weapi 就是这套)
  2. 签名:登录凭证、支付参数的防篡改签名(私钥签、公钥验)

逆向时遇到 RSA,先判断它是哪种角色——密钥交换要拿"公钥+加密逻辑",签名场景要拿"私钥"(基本拿不到,只能换思路)。

公钥的定位方法

Java 层:搜特征字符串:

"RSA/ECB/PKCS1Padding"     ← Cipher 初始化
"-----BEGIN PUBLIC KEY-----"  ← PEM 格式公钥
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."  ← base64 的公钥(MIIB 开头是特征)
"modulus" / "publicExponent"   ← 密钥工厂参数

SO 层:RSA 的大数运算有特征(模幂运算的平方-乘算法循环),但更高效的办法是搜公钥的 PEM 字符串或 base64 形式——很多 App 直接把公钥硬编码在 SO 的 rodata 段。

分段加密:RSA 的长度限制

RSA 加密的明文长度不能超过密钥长度减填充(2048 位密钥 + PKCS1 填充 = 最多 245 字节)。加密长数据必须分段

// 典型的分段加密实现
int maxBlock = 245;  // 2048/8 - 11
for (int i = 0; i < data.length; i += maxBlock) {
    byte[] block = Arrays.copyOfRange(data, i, i + maxBlock);
    cipher.doFinal(block);  // 逐段加密后拼接
}

还原时识别分段逻辑很关键——你按整段加密,App 按分段加密,结果完全不同。对拍时先用短数据(<245 字节)验证算法和密钥,再处理长数据的分段逻辑。

填充方式:PKCS1 vs OAEP

  • PKCS1Padding:老标准,主流,确定性较低(每次加密结果不同,因随机填充)
  • OAEPPadding:新标准,更安全,Android 上要指定 OAEP 的 hash 参数(SHA-1/SHA-256)

对拍失败的常见原因就是填充方式不对。Java 层 Cipher.getInstance 的字符串直接写明;SO 层靠对拍试(先试 PKCS1 再试 OAEP)。

精髓一:RSA 加密结果每次不同是正常的

PKCS1/OAEP 都有随机填充——同一明文同一公钥,每次加密结果不同。对拍时别追求密文一致,要用"私钥解密你的密文能否得到原明文"或"你的加密结果能否通过服务端校验"来验证。

精髓二:签名场景的现实路径

RSA 做签名时(私钥在 App 里,比如支付参数签名),逆向的现实路径排序:

  1. Frida 直调签名函数(首选):私钥不导出,直接借 App 的签名能力
  2. 找私钥文件:少数 App 把私钥(PKCS8 格式)放在 assets 或 SO 里——搜 -----BEGIN PRIVATE KEY-----MIIB 特征,找到就是 jackpot
  3. 放弃还原,走协议重放:签名结果有有效期的话,抓真实签名结果重放(适用面窄)

精髓三:公钥可能不是硬编码的

高级一点的 App 把公钥做"加密存储"或"网络下发":

  • 加密存储:SO 里存的是加密后的公钥,运行时解密——hook 解密函数的返回拿明文
  • 网络下发:公钥从配置接口下发——抓包直接拿,但要确认下发接口本身有没有被签名保护(鸡生蛋问题,通常下发接口走 HTTPS + 证书固定)

总结

  1. RSA 在 App 里两个角色:密钥交换(找公钥)和签名(找私钥或直调)
  2. 公钥定位搜 PEM/base64 特征串,SO 层搜 rodata
  3. 长数据分段加密(245 字节/段),对拍先短后长
  4. 加密结果每次不同是正常的(随机填充),验证用解密/服务端校验

交流微信:run1255

By 百趣云 阅读量:30 On