F06 | 安全不是可选项:理解 AES+RSA 双重加密
🆓 免费文章 | 安全篇
为什么需要加密通信?
很多新手开发者的 API 是这样的:
POST /api/user/login
{"username": "admin", "password": "111111"}
如果你的 App 在公共 WiFi 环境下运行,中间人可以直接抓包看到明文密码。即使用了 HTTPS,也存在证书伪造、中间人攻击等风险。
生产级别的应用,必须在 HTTPS 的基础上,再加一层应用层加密。
两种加密算法的特点
AES:对称加密,速度快
-
同一个密钥,既能加密也能解密
-
速度极快,适合加密大量数据(如整个请求体)
-
问题:如何安全地传递这个密钥?
AES 密钥:一把锁,既能锁也能开
问题:怎么把这把锁安全地给对方?
RSA:非对称加密,安全传递密钥
-
公钥加密,私钥解密
-
服务器公开公钥,自己保留私钥
-
速度慢,只适合加密少量数据(如 AES 密钥)
RSA 公钥:一个只能锁不能开的锁
RSA 私钥:只有服务器有的开锁钥匙
用法:把 AES 密钥放进 RSA 锁里,发给服务器
本项目的双重加密方案
前端 后端
│ │
│ 1. 生成随机 AES 密钥 (16字节) │
│ │
│ 2. 获取服务器 RSA 公钥 ──────────┤ 返回 RSA 公钥
│ │
│ 3. RSA公钥加密(AES密钥) = encryptKey
│ │
│ 4. AES加密(请求体) = encryptData │
│ │
│ 5. 发送 { encryptKey, encryptData, sign }
│ │
│ ┌─────────────┘
│ │ 6. RSA私钥解密 encryptKey → AES密钥
│ │ 7. AES解密 encryptData → 原始请求体
│ │ 8. 验证 sign 签名
│ │ 9. 处理业务逻辑
│ │ 10. AES加密响应体
│ ←─────────────────┘
│ 11. AES解密响应体 → 原始数据
关键代码剖析
前端(以 JavaScript 版为例)
// pikachuNetwork.js 核心逻辑
// 1. 生成随机 AES 密钥
function generateAESKey() {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let key = ''
for (let i = 0; i < 16; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length))
}
return key
}
// 2. RSA 加密 AES 密钥
function rsaEncrypt(data, publicKey) {
const encrypt = new JSEncrypt()
encrypt.setPublicKey(publicKey)
return encrypt.encrypt(data)
}
// 3. AES 加密请求体
function aesEncrypt(data, key) {
return CryptoJS.AES.encrypt(
JSON.stringify(data),
CryptoJS.enc.Utf8.parse(key),
{ mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }
).toString()
}
// 发送请求
async function post(url, params) {
const aesKey = generateAESKey()
const encryptKey = rsaEncrypt(aesKey, store.serverPublicKey)
const encryptData = aesEncrypt(params, aesKey)
const response = await axios.post(url, {
encryptKey,
encryptData,
sign: md5(sha1(JSON.stringify(params)))
})
// 解密响应
return aesDecrypt(response.data.encryptData, aesKey)
}
后端(Kotlin 版)
// RequestBodyAdvice:全局解密拦截器
@RestControllerAdvice
class DecryptRequestBodyAdvice : RequestBodyAdviceAdapter() {
override fun beforeBodyRead(
inputMessage: HttpInputMessage,
parameter: MethodParameter,
targetType: Type,
converterType: Class<out HttpMessageConverter<*>>
): HttpInputMessage {
// 读取请求体
val body = inputMessage.body.readBytes().toString(Charsets.UTF_8)
val request = objectMapper.readValue(body, EncryptRequest::class.java)
// RSA 解密 AES 密钥
val aesKey = rsaDecrypt(request.encryptKey, privateKey)
// AES 解密请求体
val decryptedBody = aesDecrypt(request.encryptData, aesKey)
return DecryptedHttpInputMessage(inputMessage, decryptedBody)
}
}
签名验证:防篡改
除了加密,还需要防止请求被篡改:
// 前端生成签名
function signature(pwd) {
return md5(sha1(pwd).toUpperCase())
}
// 后端验证签名
fun verifySign(params: Map<String, Any>, sign: String): Boolean {
val calculated = md5(sha1(params.toSortedString()).toUpperCase())
return calculated == sign
}
给初学者的建议
你不需要现在就完全理解每一行加密代码。在付费课程里,我们会:
- 直接给你完整的加密工具类代码
- 解释每个步骤的用途
- 手把手接入到你的项目里
现在只需要记住:生产级的 API 必须加密,本课程提供了完整的可复用实现。
下一篇
本文为免费文章,转载请注明出处。