实战封装加密工具类
大约 3 分钟
实战:封装加密工具类(URL 传参与登录密码场景)
场景 1:URL 传参防篡改
路由跳转携带敏感参数(如 ?userId=1&orderId=100),直接暴露容易被猜测和篡改。思路:AES 加密参数 → Base64 → URL 安全处理,接收方解密还原。
utils/crypto.js(完整工具类)
import CryptoJS from "crypto-js";
// 密钥与 IV:前后端约定,16 字节(128 位 AES);生产环境不要硬编码在前端包里,
// 更稳妥的做法是接口下发或走 HTTPS + 服务端签名
const SECRET_KEY = CryptoJS.enc.Utf8.parse("1234567890abcdef");
const SECRET_IV = CryptoJS.enc.Utf8.parse("abcdef1234567890");
/**
* AES 加密(CBC 模式,PKCS7 填充),输出 Base64
* 固定 key+iv,同一明文密文相同,方便作为 URL 参数还原
*/
export function encrypt(plainText) {
if (!plainText) return "";
const encrypted = CryptoJS.AES.encrypt(
CryptoJS.enc.Utf8.parse(String(plainText)),
SECRET_KEY,
{
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
return encrypted.toString(); // Base64 密文
}
/**
* AES 解密,失败返回空字符串
*/
export function decrypt(cipherText) {
if (!cipherText) return "";
try {
const decrypted = CryptoJS.AES.decrypt(cipherText, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
} catch (e) {
return "";
}
}
/**
* 对象参数加密:{ userId: 1 } -> "5U2Jk9..."(拼在 ?params= 后)
*/
export function encryptParams(params) {
return encodeURIComponent(encrypt(JSON.stringify(params)));
}
/**
* 对象参数解密:密文 -> { userId: 1 }
*/
export function decryptParams(cipher) {
try {
return JSON.parse(decodeURIComponent(decrypt(cipher)));
} catch (e) {
return null;
}
}
/**
* MD5 签名(不可逆):给参数生成摘要,配合时间戳防重放
*/
export function signParams(params, salt = "blog-salt") {
const sortedKeys = Object.keys(params).sort();
const raw = sortedKeys.map(k => `${k}=${params[k]}`).join("&") + `&salt=${salt}`;
return CryptoJS.MD5(raw).toString();
}
使用:路由跳转
import { encryptParams } from "@/utils/crypto";
// 加密跳转
const query = encryptParams({ userId: 1, orderId: 100 });
router.push(`/order-detail?params=${query}`);
// 地址栏看到的是 /order-detail?params=U2FsdGVkX1%2F8x3...(无明文信息)
使用:目标页解密
import { decryptParams } from "@/utils/crypto";
const route = useRoute();
const params = decryptParams(route.query.params);
if (!params) {
// 解密失败:参数被篡改或过期
router.replace("/403");
} else {
console.log(params.userId, params.orderId); // 1 100
}
场景 2:登录密码传输
原则:前端不负责密码的最终安全(HTTPS 才是根基),但要避免明文直传。
常见方案:前端 MD5/SHA 加盐摘要后传输,服务端比对摘要:
import { signParams } from "@/utils/crypto";
export async function login(username, password) {
return axios.post("/login", {
username,
// 加盐摘要,避免明文密码出现在请求体/日志中
password: signParams({ username, timestamp: Date.now() }, password)
});
}
更严格的方案是 RSA 非对称加密(crypto-js 不支持,用 jsencrypt):
npm install jsencrypt
import JSEncrypt from "jsencrypt";
// 公钥由服务端下发(前端只加密,私钥只有服务端有,抓包也解不开)
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...(服务端下发)
-----END PUBLIC KEY-----`;
const encryptor = new JSEncrypt();
encryptor.setPublicKey(PUBLIC_KEY);
export function rsaEncrypt(text) {
return encryptor.encrypt(text); // 返回密文字符串,加密失败返回 false
}
// 登录:rsaEncrypt(JSON.stringify({ username, password, timestamp }))
前端加密的安全边界(重要)
- 前端加密 ≠ 安全:密钥放在前端代码里,打开 devtools 就能拿到。它能防"肉眼直接看懂",防不了恶意用户
- 传输安全的根基是 HTTPS,前端加密只是纵深防御的一层
- 密码不要用纯 MD5(彩虹表可查),至少加盐 + 多次迭代,更好的是服务端 bcrypt/scrypt/argon2
- crypto-js 适合:参数混淆、防爬虫、签名校验、本地缓存的轻度加密;真正的鉴权交给 token/HTTPS
Loading...
