前端调用接口时怎么对请求参数加密才安全?

程序猿冬冬 阅读 168

我们后端要求所有敏感接口的请求参数必须加密传输,但我试了用 AES 加密后传过去,后端说解密失败。我是在浏览器里用 CryptoJS 做的加密,密钥直接写在代码里,是不是哪里不对?

这是我的加密代码:

const key = CryptoJS.enc.Utf8.parse('mySecretKey12345');
const encrypted = CryptoJS.AES.encrypt(JSON.stringify(params), key, {
  mode: CryptoJS.mode.ECB,
  padding: CryptoJS.pad.Pkcs7
}).toString();

但这样真的安全吗?而且后端用 Java 解密一直报错,是不是加密方式不匹配?

我来解答 赞 4 收藏
二维码
手机扫码查看
2 条解答
程序猿雯雯
你这个问题很典型,我来帮你分析一下。

问题在哪里

先说最可能的原因:CryptoJS 的 AES.encrypt 默认返回的是 OpenSSL 兼容的加密格式,这个格式会在密文前面加上 "Salted__" + 8字节salt + 实际密文,然后整体 Base64 编码。但 Java 那边如果直接用标准 AES 解密,不处理这个头,就会报错。

另外,你的密钥 'mySecretKey12345' 是 15 个字符,AES 要求密钥长度必须是 16/24/32 字节。CryptoJS 会自动补齐,但补齐方式可能和 Java 那边不一致。

解决方案

推荐用 CBC 模式 + PKCS7 填充(Java 叫 PKCS5Padding),这样两端更容易对齐。

前端代码:

// 密钥必须是 16/24/32 字节,这里用 32 字节
const keyStr = 'mySecretKey12345mySecretKey12345'; // 32 字符
const key = CryptoJS.enc.Utf8.parse(keyStr);

// 生成随机的 IV,16 字节
const iv = CryptoJS.lib.WordArray.random(16);

// 要加密的参数
const params = { username: 'test', password: '123456' };
const data = JSON.stringify(params);

// 加密
const encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});

// 把 IV 和密文拼接后 Base64 编码一起传过去
// 格式:IV(hex) + ':' + ciphertext(base64)
const result = iv.toString(CryptoJS.enc.Hex) + ':' + encrypted.ciphertext.toString(CryptoJS.enc.Base64);

console.log(result); // 这就是你要传给后端的


对应的 Java 解密代码:

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtil {

private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String KEY = "mySecretKey12345mySecretKey12345"; // 必须和前端一致,32字节

public static String decrypt(String encryptedData) throws Exception {
// 分割 IV 和密文
String[] parts = encryptedData.split(":");
byte[] iv = hexToBytes(parts[0]); // IV 是 16 字节
byte[] ciphertext = Base64.getDecoder().decode(parts[1]);

// 初始化 Cipher
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);

cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

// 解密
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted, "UTF-8");
}

private static byte[] hexToBytes(String hex) {
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i+1), 16));
}
return data;
}
}


关于安全性

你密钥直接写代码里是最大的问题。浏览器代码可以被任何人看到,包括这个密钥。线上环境这样做等于没加密。

真正安全的方式:

1. 用 HTTPS,这是基础
2. 密钥从后端动态获取,每次请求可以用不同的临时密钥(或者用后端返回的公钥做非对称加密来传递对称密钥)
3. 可以在后端实现一个 /getKey 接口,返回当前会话用的加密密钥,前端每次加密前先调这个接口拿密钥

简单说,HTTPS + 动态密钥才是正经做法,前端单独做 AES 加密也就是防防君子,真正的安全还是得靠传输层加密。
点赞
2026-03-13 12:15
南宫雅涵
你的问题很典型,前端加密参数解密失败基本都是因为前后端实现细节不匹配。

先说解密失败的原因。你用的是 ECB 模式 + PKCS7 填充,CryptoJS 的 AES.encrypt().toString() 实际输出的是 Base64 编码的密文。Java 端解密时要先做 Base64 解码,再用 AES/ECB/PKCS5Padding 解密。

Java 代码大概长这样:

// 密钥要转成字节数组
byte[] keyBytes = "mySecretKey12345".getBytes("UTF-8");
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");

// 先 Base64 解码
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedString);

// 解密
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decrypted = cipher.doFinal(encryptedBytes);
String result = new String(decrypted, "UTF-8");


如果后端报填充错误,大概率是 Java 用了错误的填充方式,或者没做 Base64 解码直接解密了。

再说你关心的安全性问题。密钥直接写死在前端代码里是最大的隐患——用户打开浏览器开发者工具就能看到你的密钥和加密逻辑,这等于没加密。攻击者完全可以复用你的加密方法伪造请求。

真正安全的做法是:前端用后端提供的公钥做 RSA 非对称加密,或者用后端返回的临时对称密钥配合 HTTPS 做加密。单纯在前端硬编码密钥防君子不防小人,没多大意义。

如果你们只是防抓包工具简单加密一下,ECB 模式其实也够用,但密钥必须动态获取,别写死。
点赞
2026-03-12 09:09