本文介绍一种JS前端接口请求参数混淆方案,采用AES对称加密处理数据,结合RSA非对称加密保护密钥,防止信息泄露与参数篡改。方案适用于Post、Put、Delete等修改数据库的接口,通过请求拦截器在发送前处理,后端使用Egg.js中间件解密验证,提升应用安全性。
前言
在部分接口调用场景中,为规避数据以明文形式传输,需对请求参数实施混淆或加密措施,由服务端接收后执行解密以还原原始信息。
此举旨在保障数据安全并增加明文破解难度。以用户登录接口为例,若账号密码明文传输易引发安全隐患,且需防范参数伪造请求,故前端参数混淆至关重要,本文分享一种个人实践方案,主要目标包括:
- 规避信息泄露风险
- 阻断参数被非法篡改
- 增强应用整体安全性与稳定性
针对加密或混淆算法的选择,诸如base64、MD5等安全性较低或不可逆的算法不符合本场景需求,应采用Aes与Rsa相结合的策略,以达成数据加解密目标。
需处理的接口参数范围
基于安全考量,凡涉及数据库新增、修改或删除操作的接口,均须执行混淆或加密处理,此类接口通常表现为post、put、delete等请求类型。
针对get请求,若接口设计规范,仅用于数据获取且无数据库直接操作,则无需进行参数处理。
此外,post请求及部分文件上传等无需参数处理的接口需特殊对待;同时,在开发环境中为便于调试,亦无需执行处理。
参数处理机制
鉴于Rsa在处理大规模数据时优势不显著,适用于小数据量场景,故参数数据处理选用Aes加密,而参与加密的密钥key则通过Rsa非对称加密技术提升破解难度。
相关依赖库及其版本号如下:
"crypto-js": "^4.1.1", "jsencrypt": "^3.2.1"
本方案流程为:先将原始数据转换为query格式字符串,随后以随机字符串作为密钥进行Aes加密,截取特定子串作为原始密钥,再次执行Aes加密,最终利用与后端约定的公钥对原始密钥进行Rsa加密。具体流程及算法如下:
- 对参数排序并提取
query字符串进行处理 - 利用随机字符串加密提取的字符串并截取,生成密钥
- 使用生成的密钥对原始参数执行
Aes加密 - 对密钥实施
Rsa非对称加密 - 输出最终的
data与key
/**
* 加密请求数据
* @param {Object} rawData 原始数据
* @returns {data, key}
*/
export function encryptRequestData(rawData) {
// 字典排序并赋值
var result = {}, str = [], arr = Object.keys(rawData).sort();
arr.map(m => { result[m] = rawData[m] });
// 处理成 query 形式字符串
for (var p in result)
result.hasOwnProperty(p) && str.push(encodeURIComponent(p) + "=" + encodeURIComponent(result[p]));
result = str.join("&");
// 参与 Aes 加密的密钥,将处理后的字符串用 16 位随机码对称加密,再从第 3 位开始获取 16 位原始密钥
const rawKey = aesEncrypt(result, randomString(16)).substr(3, 16);
// 输出最后的加密参数
const data = aesEncrypt(JSON.stringify(rawData), rawKey);
const key = rsaEncrypt(rawKey);
return { data, key }
}
Aes加密实现
/**
* Aes 加密
* @param {String} data
* @param {String} customer_key
* @returns encrypted
*/
export function aesEncrypt(data, customer_key = "") {
var key = CryptoJS.enc.Utf8.parse(customer_key);
var messageHex = CryptoJS.enc.Utf8.parse(data);
var encrypted = CryptoJS.AES.encrypt(messageHex, key, {
"mode": CryptoJS.mode.ECB,
"padding": CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
Rsa加密实现
/**
* Rsa 加密
*/
export function rsaEncrypt(rawData) {
let data;
try {
var rsa = new JsEncrypt();
rsa.setPublicKey(publicPem);
data = rsa.encrypt(rawData)
} catch (error) {
return null
}
return data;
}
签名验证机制
若需进一步强化防篡改能力,可在参数处理过程中通过特定算法生成签名sign值并随请求提交。后端解密后,利用相同算法对解密数据生成sign值并与提交值比对,以验证请求合法性。此处不作深入展开。
处理时机控制
需在请求拦截阶段对参数执行混淆处理,仅针对post请求及非本地环境生效。为便于调试,除开发环境外,在实例配置中增加了uncrypt标识,用于指定无需处理的接口。
// 请求拦截
instance.interceptors.request.use(
config => {
config.headers.contentType = "application/x-www-form-urlencoded"
const token = local.get('token')
token && (config.headers.Authorization = token)
const { method, uncrypt = false, data = {} } = config;
(method === 'post' && !uncrypt && cfg.NODE_ENV === 'development') && (config.data = encryptRequestData(data));
return config
},
error => Promise.error(error)
)
后端实现逻辑
前端处理后的参数以data和key构成的对象形式提交至后端,服务层接收后执行解密。此处以Egg.js框架实现为例。涉及依赖及其版本号如下:
"crypto-js": "^4.1.1", "node-rsa": "^1.1.1"
Rsa解密实现
// RSA 解密
rsaDecrypt(data) {
let dataObj;
return new Promise(function (resolve, reject) {
// 私钥存放在app/extend/pem/private.pem
fs.readFile('app/extend/pem/private.pem', function (err, pem) {
const key = new NodeRSA(pem, 'pkcs8-private');
key.setOptions({ encryptionScheme: 'pkcs1' });
try {
dataObj = key.decrypt(data, 'utf8');
} catch (e) {
const second = new NodeRSA(pem, 'pkcs8-private');
try {
dataObj = second.decrypt(data, 'utf8');
} catch (error) {
reject("Rsa 解密失败");
}
}
resolve(dataObj);
});
});
}
Aes解密实现
// Aes 解密
aesDecrypt(data, customer_key = "") {
var key = CryptoJS.enc.Utf8.parse(customer_key || this.config.secret.aes.key);
var decrypt = CryptoJS.AES.decrypt(data, key, {
"mode": CryptoJS.mode.ECB,
"padding": CryptoJS.pad.Pkcs7
});
return CryptoJS.enc.Utf8.stringify(decrypt);
}
处理中间件配置
新建解密处理中间件app/middleware/security.js
module.exports = () => {
return async function (ctx, next) {
const { helper, request } = ctx;
const { ajaxMsg } = helper;
const { key, data } = request.body;
if (!key || !data) return ajaxMsg(ctx, "-1", "请求参数错误", null, 400);
let rawKey;
try {
rawKey = await helper.rsaDecrypt(key);
} catch (error) {
return ajaxMsg(ctx, "-1", "密钥解析失败", null, 400)
}
if (!rawKey) return ajaxMsg(ctx, "-1", "密钥解析失败", null, 400);
const decryptData = JSON.parse(helper.aesDecrypt(data, rawKey));
if (!decryptData) return ajaxMsg(ctx, "-1", "安全验证未通过", null, 400);
request.body = decryptData;
await next();
};
};
路由中使用方式
const { router, controller, middleware } = app;
const security = middleware.security(); // 接口参数加密
router.post('/common/user/login', security, controller.common.user.login); // 登录以上为JS前端接口请求参数混淆方案的完整内容,更多JS前端接口请求参数混淆相关信息请关注本站其他文章!