:AES 和 RSA 到底怎么配合?一次讲清 App 接口混合加密)
前面几篇密码与加密基础篇1别再说 MD5 加密了编码、摘要、加密到底有什么区别-CSDN博客密码与加密基础篇2密码到底怎么存为什么 MD5 已经过时-CSDN博客密码与加密基础篇3salt 和 pepper 到底是什么为什么 salt 可以放数据库-CSDN博客密码与加密基础篇4bcrypt / Argon2id 为什么比 MD5 更适合存密码-CSDN博客我们一直在讲密码存储第1篇别再说 MD5 加密了编码、摘要、加密到底有什么区别 第2篇密码到底怎么存为什么 MD5 已经过时 第3篇salt 和 pepper 到底是什么为什么 salt 可以放数据库 第4篇bcrypt / Argon2id 为什么比 MD5 更适合存密码这几篇主要解决的是密码怎么存为什么不能明文存为什么不能简单 MD5为什么要用 bcrypt / Argon2id。这一篇开始进入真正的“加密传输 / 业务层接口加密”。很多开发一听到接口加密就会想到AES RSA HTTPS 公钥 私钥 密文 签名但真正落到 App 和后端接口设计里经常会卡在一个问题上AES 和 RSA 到底怎么配合这篇文章就讲清楚这个问题。一句话先说结论AES 负责加密真正的业务数据RSA 负责加密 AES key。或者再短一点AES 加密数据RSA 加密 AES 密钥。一、先区分AES 和 RSA 分别是什么AES 和 RSA 都和加密有关但它们不是一类东西。AES 是对称加密。RSA 是非对称加密。二、AES对称加密适合加密数据AES 的特点是同一个 key 加密 同一个 key 解密流程是明文数据 ↓ AES key 加密 密文数据 ↓ AES key 解密 明文数据所以 AES 叫对称加密。比如 App 要加密一个登录请求{ username: wu, password: 123456 }可以用 AES-GCM 加密成cipherText AES-GCM(username/password JSON, aesKey, iv)AES 的优点是速度快 适合加密大量数据 适合加密 JSON 请求体 适合加密文件 适合本地 Token 加密存储但 AES 有一个核心问题AES key 怎么安全地给后端因为 AES 加密和解密用的是同一把 key。如果 App 和后端都要解密请求体那么 App 和后端都要知道这把 AES key。问题来了AES key 写死在 App 里不行。因为 APK 可以反编译。AES key 明文传给后端也不行。因为传 key 本身就暴露了。所以 AES 很适合加密数据但它自己解决不了“密钥安全传递”的问题。三、RSA非对称加密适合保护密钥RSA 和 AES 不一样。RSA 有一对 key公钥 public key 私钥 private key它的典型用法有两种公钥加密私钥解密 私钥签名公钥验签如果用于接口加密常见方式是后端保存 RSA 私钥 App 内置或动态获取 RSA 公钥App 用后端公钥加密数据。后端用自己的私钥解密数据。因为私钥只在后端App 不需要知道私钥。这就解决了一个问题公钥可以给客户端 私钥只在服务端但 RSA 也有明显缺点速度慢 不适合加密大量数据 可加密的数据长度有限所以 RSA 不适合直接加密整个请求体。比如你有一个很大的 JSON甚至文件上传、图片、设备数据、日志数据都不适合直接用 RSA 全量加密。因此RSA 更适合加密少量数据。最典型的少量数据就是AES key四、为什么不能只用 AES只用 AES 最大的问题是AES key 怎么安全给后端错误做法一private const val AES_KEY 1234567890123456这样把 AES key 写死在客户端风险很大。因为 APK 可以被反编译。一旦 key 被拿到所有使用这把 key 加密的数据都有风险。错误做法二{ aesKey: 1234567890123456, cipherText: xxxx }这等于把钥匙和保险箱一起寄出去了。没有意义。所以只用 AES 不够。AES 需要一个安全传递密钥的方案。五、为什么不能只用 RSA只用 RSA 的问题是RSA 不适合加密大量数据。比如登录请求体还比较小{ username: wu, password: 123456 }看起来好像可以直接 RSA 加密。但真实业务接口可能是{ deviceId: xxx, location: { lat: 31.2, lng: 120.6 }, data: [ ... ], extra: { ... } }请求体可能很大。RSA 加密大数据性能差而且有长度限制。所以工程里不会用 RSA 加密所有业务数据。RSA 更适合做加密 AES key 签名 验签 密钥交换六、AES RSA 的经典配合AES 和 RSA 的经典配合就是AES 加密业务数据 RSA 加密 AES key也就是混合加密。流程如下App 生成一个随机 AES key ↓ App 用 AES-GCM 加密业务数据 ↓ App 用后端 RSA 公钥加密 AES key ↓ App 发送 encryptedKey iv cipherText 给后端 ↓ 后端用 RSA 私钥解密 encryptedKey得到 AES key ↓ 后端用 AES key iv 解密 cipherText得到业务明文这就是 AES 和 RSA 最经典的配合方式。它结合了两者优点AES 加密业务数据速度快。 RSA 加密 AES key解决密钥传递问题。一句话RSA 不直接加密大数据而是保护 AES key真正的数据加密交给 AES。七、一次 App 登录请求怎么做混合加密假设登录请求原文是{ username: wu, password: 123456 }如果做业务层 AES RSA 混合加密App 端可以这样处理1. 把登录参数转成 JSON 字符串 2. 随机生成一个临时 AES key 3. 随机生成一个 IV 4. 使用 AES-GCM 加密登录 JSON得到 cipherText 5. 使用后端 RSA 公钥加密 AES key得到 encryptedKey 6. 把 encryptedKey、iv、cipherText 发给后端最终请求体可能变成{ encryptedKey: RSA公钥加密后的AES key, iv: AES-GCM使用的IV, cipherText: AES-GCM加密后的登录请求体, timestamp: 1780000000000, nonce: random-string }这时网络上传输的就不是{ username: wu, password: 123456 }而是密文。八、后端怎么解密后端收到请求后1. 读取 encryptedKey 2. 使用 RSA 私钥解密 encryptedKey得到 AES key 3. 读取 iv 和 cipherText 4. 使用 AES key iv 解密 cipherText 5. 得到原始 JSON 6. 继续走登录逻辑解密后后端拿到{ username: wu, password: 123456 }然后后端再做密码验证用户输入密码 ↓ bcrypt / Argon2id / PBKDF2 校验 ↓ 登录成功 ↓ 签发 accessToken / refreshToken注意这里不要混。AES RSA 解决的是请求体在业务层加密传输bcrypt / Argon2id 解决的是密码在数据库中的安全存储和验证它们不是同一层问题。九、encryptedKey、iv、cipherText 分别是什么一次混合加密请求里常见字段有三个。1. encryptedKeyencryptedKey RSA公钥加密后的AES key它的作用是把本次请求使用的 AES key 安全交给后端。App 生成 AES key 后不能明文发给后端。所以要用后端 RSA 公钥加密。后端用 RSA 私钥才能解开。2. iviv AES-GCM 使用的初始化向量IV 不需要保密。但同一个 AES key 下IV 不能重复。常见做法是每次加密都随机生成一个 IV。IV 要和 cipherText 一起发给后端。因为后端解密时也需要它。3. cipherTextcipherText AES-GCM 加密后的业务数据比如原始请求是{ username: wu, password: 123456 }经过 AES-GCM 后就变成 cipherText。后端拿到 AES key 和 iv 后才能解密出原始请求体。十、timestamp 和 nonce 有什么用很多加密请求里还会带{ timestamp: 1780000000000, nonce: random-string }它们主要用于防重放。因为即使请求体被加密如果攻击者截获了一整包请求理论上可能重复发送这包请求。比如重复提交encryptedKey iv cipherText如果后端不做任何校验可能无法区分这是新请求还是旧请求重放。timestamp 和 nonce 的作用是timestamp 判断请求是否过期比如只允许 5 分钟内有效。 nonce 随机字符串后端记录已使用 nonce防止同一请求重复提交。所以更完整的业务层加密请求通常会考虑加密 防篡改 防重放 过期控制AES-GCM 本身可以提供密文完整性校验。timestamp nonce 可以进一步防止重放。十一、请求级混合加密上面讲的是一种常见方案每个请求都生成一个临时 AES key这叫请求级混合加密。流程是每次请求 生成 AES key AES 加密数据 RSA 加密 AES key 后端 RSA 解 AES key 后端 AES 解数据优点简单 无状态 每个请求独立 后端不用保存会话 AES key缺点每个请求都要 RSA 解密 性能成本更高 请求体更大适合登录 改密码 支付 提现 绑定银行卡 提交隐私信息 设备控制指令也就是敏感接口。十二、会话级混合加密如果所有接口都要求业务层加密每个请求都 RSA 一次成本就比较高。这时可以考虑会话级混合加密。流程是1. App 启动或登录前请求后端公钥 2. App 生成 sessionAesKey 3. App 用 RSA 公钥加密 sessionAesKey 4. 后端 RSA 私钥解密拿到 sessionAesKey 5. 后端生成 secureSessionId 6. 后端把 secureSessionId - sessionAesKey 存 Redis设置过期时间 7. 后续请求 Header 带 secureSessionId 8. 后续请求体都用 sessionAesKey 做 AES-GCM 加密也就是第一次用 RSA 交换 AES key 后续全部用 AES 加密业务数据优点性能更好 适合全接口加密 后续请求体更小缺点后端要维护 secureSession 要处理 session 过期 要处理重连 要处理多设备 要处理 Redis 丢失 设计复杂度更高适合金融类 App 政企系统 IoT 设备控制 全接口业务层加密场景普通业务系统不一定需要做到这一步。十三、是不是所有接口都要 AES RSA 加密不一定。这要看业务安全等级。1. 普通 App普通 App 通常采用HTTPS Token 认证 服务端密码哈希 Token 本地加密存储 日志脱敏就已经能覆盖大部分场景。不一定需要所有接口再做 AES RSA 业务加密。2. 中等安全 App可以只对敏感接口加密比如登录 注册 改密码 支付 提现 绑定银行卡 提交身份证 提交隐私信息 设备控制指令这时可以采用请求级混合加密。3. 高安全 App如果业务要求所有接口都不能以明文 JSON 形式出现在业务层可以做全接口加密。这时更适合会话级混合加密。比如先建立 secureSession 后续所有请求体 AES-GCM 加密十四、AES RSA 和 HTTPS 是什么关系这是最容易混淆的点。HTTPS 解决的是传输层安全AES RSA 业务加密解决的是业务层请求体加密可以这样理解HTTPS 保护 App 到服务器之间的网络传输通道。 AES RSA 即使在业务层看到请求体也看到的是密文。普通项目里HTTPS 已经是必须的。即使做了 AES RSA也不能替代 HTTPS。不要这样想我做了 AES RSA所以可以不用 HTTPS。这是错误的。正确理解是HTTPS 是底线。 AES RSA 是在 HTTPS 之上的额外业务层保护。所以完整结构是业务 JSON ↓ AES-GCM 业务层加密 业务密文 ↓ HTTPS/TLS 传输层加密 网络传输这是两层不同的保护。十五、AES RSA 和 Token 本地加密有什么区别这也是很容易混的点。前面移动端登录态安全篇里讲过Token 本地存储 AES-GCM Android Keystore这里又讲接口加密 AES-GCM RSA它们看起来都用了 AES-GCM但不是一回事。1. Token 本地加密目标是保护 App 本地保存的 Token结构是Android Keystore ↓ 保护本地 AES key AES-GCM ↓ 加密 accessToken / refreshToken DataStore / MMKV ↓ 保存 Token 密文这里的 AES key 是本机本地使用。后端不需要知道。2. 接口业务层加密目标是保护 App 和后端之间的业务请求体结构是App 生成临时 AES key ↓ AES-GCM 加密请求体 ↓ RSA 公钥加密 AES key ↓ 后端 RSA 私钥解 AES key ↓ 后端 AES-GCM 解请求体这里的 AES key 是 App 和后端通信使用。后端必须知道。所以要记住本地 Token 加密 AES key 由 Android Keystore 保护后端不需要知道。 接口业务加密 AES key 需要通过 RSA 公钥加密后传给后端后端需要解出来。它们不是同一把 AES key。不要混用。十六、App 端伪代码下面用伪代码表达一次请求级混合加密。data class EncryptRequest( val encryptedKey: String, val iv: String, val cipherText: String, val timestamp: Long, val nonce: String )App 加密流程fun encryptRequest(jsonBody: String, serverPublicKey: PublicKey): EncryptRequest { // 1. 随机生成 AES key val aesKey generateRandomAesKey() // 2. 随机生成 IV val iv generateRandomIv() // 3. AES-GCM 加密业务 JSON val cipherText aesGcmEncrypt( plainText jsonBody, key aesKey, iv iv ) // 4. RSA 公钥加密 AES key val encryptedKey rsaEncrypt( data aesKey.encoded, publicKey serverPublicKey ) // 5. 组装请求 return EncryptRequest( encryptedKey base64(encryptedKey), iv base64(iv), cipherText base64(cipherText), timestamp System.currentTimeMillis(), nonce randomNonce() ) }这里重点不是代码细节而是流程先 AES 加密数据 再 RSA 加密 AES key十七、后端伪代码后端解密流程public String decryptRequest(EncryptRequest request) { // 1. Base64 解码 encryptedKey byte[] encryptedKeyBytes base64Decode(request.getEncryptedKey()); // 2. RSA 私钥解密 AES key byte[] aesKeyBytes rsaDecrypt( encryptedKeyBytes, serverPrivateKey ); // 3. Base64 解码 iv 和 cipherText byte[] iv base64Decode(request.getIv()); byte[] cipherText base64Decode(request.getCipherText()); // 4. AES-GCM 解密请求体 String plainJson aesGcmDecrypt( cipherText, aesKeyBytes, iv ); return plainJson; }解出来的 plainJson 才是原始业务请求体。比如登录接口最终拿到{ username: wu, password: 123456 }然后再进入正常业务逻辑。十八、RSA 加密建议用什么填充工程里不要使用老旧不安全的 RSA 填充方式。更推荐RSA/ECB/OAEPWithSHA-256AndMGF1Padding不要使用RSA/ECB/PKCS1Padding如果团队或平台兼容性有限也要明确知道当前使用方案的安全边界和兼容成本。同样AES 建议使用AES/GCM/NoPadding而不是AES/ECB/PKCS5Padding基本原则是AES 不用 ECB。 RSA 优先 OAEP。 随机数使用安全随机数。 密钥不要写死在客户端。十九、公钥怎么给 App后端 RSA 公钥可以有几种方式给 App。1. 内置在 AppApp 里内置后端公钥。优点简单 不需要额外接口缺点公钥轮换不方便 App 旧版本可能一直使用旧公钥2. 通过接口动态获取比如GET /security/public-key后端返回{ keyId: v1, publicKey: xxxxx }App 加密请求时带上{ keyId: v1, encryptedKey: xxx, iv: xxx, cipherText: xxx }后端根据 keyId 找对应私钥解密。优点支持公钥轮换 适合多版本密钥管理缺点需要考虑公钥接口防篡改 依然必须走 HTTPS这里再次强调公钥可以公开。 私钥绝对不能给客户端。二十、这套方案里谁应该保密需要保密的是RSA 私钥 服务端密钥材料 AES key 明文 Token 明文 用户密码不需要保密但要保证正确性的有RSA 公钥 IV keyId 算法版本 timestamp nonceIV 不需要保密但不能乱用。同一个 AES key 下IV 不能重复。timestamp 和 nonce 不一定保密但后端要校验它们防止重放。二十一、加密后还要不要签名如果使用 AES-GCM它本身提供认证加密能力可以发现密文被篡改。但在一些更复杂的接口安全体系里可能还会额外做签名。比如sign HMAC-SHA256(encryptedKey iv cipherText timestamp nonce, secret)这样可以进一步明确请求完整性和身份认证。不过不要一上来就堆很多东西。普通项目先把这些基础做好HTTPS Token 认证 密码哈希 Token 本地加密 日志脱敏 必要敏感接口 AES RSA如果业务安全要求更高再设计完整签名、防重放、密钥轮换体系。二十二、常见误区误区 1AES 和 RSA 二选一不对。它们不是二选一而是分工合作。AES 加密数据 RSA 加密 AES key误区 2RSA 直接加密所有请求体不推荐。RSA 慢而且有长度限制。大数据加密交给 AES。误区 3AES key 写死在 App 里错误。APK 可以反编译写死 key 风险很大。误区 4做了 AES RSA 就不用 HTTPS错误。HTTPS 是底线。AES RSA 是业务层额外保护。误区 5Token 本地 AES key 和接口 AES key 是一回事错误。本地 Token 加密的 AES key 由 Android Keystore 保护后端不需要知道。接口加密的 AES key 需要通过 RSA 加密传给后端后端要用它解密请求体。误区 6公钥不能给客户端不对。公钥可以给客户端。私钥不能给客户端。二十三、最终总结AES 和 RSA 到底怎么配合一句话AES 加密业务数据RSA 加密 AES 密钥。为什么这样设计因为AES 速度快适合加密大量数据但 key 传递是问题。 RSA 适合保护少量数据比如 AES key但不适合加密大量业务数据。所以工程上通常是App 生成临时 AES key ↓ AES-GCM 加密请求体 ↓ RSA 公钥加密 AES key ↓ 后端 RSA 私钥解出 AES key ↓ 后端 AES-GCM 解出请求体再放到完整安全体系里密码存储 bcrypt / Argon2id / PBKDF2 登录传输 HTTPS 业务层接口加密 AES-GCM RSA Token 本地存储 AES-GCM Android Keystore 接口认证 accessToken / refreshToken Token 失效 Redis / jti / tokenVersion 日志安全 敏感字段脱敏最后再记住一句HTTPS 是底线AES RSA 是业务层增强Token 本地加密和接口业务加密不是一回事不要混用同一套 key。