1. 微信小程序AES加密的常见坑点
第一次在微信小程序里用AES加密时,我遇到了一个让人抓狂的问题:无论怎么调整CryptoJS版本,加密功能就是跑不起来。报错信息像天书一样,从4.0版本降到3.1.4版本都不管用。后来才发现,微信小程序的JavaScript运行环境对第三方库的支持有特殊要求,不是所有版本都能兼容。
经过反复测试,最终锁定CryptoJS 3.3.0版本在小程序里最稳定。这里有个细节要注意:安装完包之后,必须通过微信开发者工具的"构建npm"功能生成miniprogram_npm目录,否则require会报错。我当初就是漏了这一步,白白折腾了半天。具体操作路径是:工具栏 → 工具 → 构建npm,看到终端提示完成才算成功。
密钥和偏移量(IV)的处理也是个暗坑。刚开始我直接用了字符串当密钥,结果加解密死活对不上。后来才明白必须先用CryptoJS.enc.Utf8.parse()方法转换成WordArray对象。这里有个实用技巧:密钥长度必须是16/24/32字节(对应128/192/256位),如果后端给的密钥长度不对,可以用MD5先做一次哈希转换。
2. AES-CBC模式的完整封装方案
CBC模式相比ECB更安全,但实现起来也复杂些。每个数据块加密前都要与前一个密文块做异或运算,所以需要初始化向量IV。这里我封装了一个实战验证过的工具类,关键点都加了注释:
// utils/aes.js const Crypto = require('crypto-js') // 建议将密钥配置在服务器下发的配置中 const key = Crypto.enc.Utf8.parse('你的16位密钥字符串') const iv = Crypto.enc.Utf8.parse('你的16位偏移量') function encrypt(text) { const encrypted = Crypto.AES.encrypt(text, key, { iv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 }) return encrypted.toString() } function decrypt(encryptedStr) { const decryptResult = Crypto.AES.decrypt(encryptedStr, key, { iv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 }) return decryptResult.toString(Crypto.enc.Utf8) } module.exports = { encrypt, decrypt }实际使用时发现三个易错点:1) 加密结果必须用toString()转成字符串;2) 解密后要用Utf8编码转换;3) PKCS7填充在数据长度正好是16倍数时反而会填充16字节。曾经有个生产环境bug就是因为没处理这个边界情况,导致解密失败。
3. 文件安全分享的加密实践
小程序里分享敏感文件时,直接传原始URL风险很大。我们的解决方案是:先用AES加密文件标识符,生成临时token作为参数传递。比如要分享用户ID为123的PDF文件,实际分享的链接会是:
https://yourdomain.com/share?token=U2FsdGVkX1+3C7gT5Jj7w9W8Lz4a1xYr后端收到请求后先用相同密钥解密token获取真实文件ID。这样做的好处是:1) 真实ID不会暴露;2) token可设置时效;3) 相同文件每次加密结果不同,防止被爬取。
具体实现时要注意文件流加密的性能问题。大文件建议分块处理,每4MB为一个chunk,用相同的IV加密,最后合并。我们测试过,这种方式在iOS和Android端都能保持流畅:
// 文件分片加密示例 async function encryptFile(filePath) { const chunkSize = 4 * 1024 * 1024 const fileManager = wx.getFileSystemManager() const fileInfo = fileManager.statSync(filePath) let encryptedChunks = [] for (let i = 0; i < fileInfo.size; i += chunkSize) { const chunk = fileManager.readFileSync(filePath, { position: i, length: chunkSize }) encryptedChunks.push(encrypt(chunk)) } return encryptedChunks.join('') }4. 密钥安全管理方案
最危险的错误就是把密钥硬编码在前端代码里。我们现在的做法是:
- 启动时从服务端动态获取密钥
- 用wx.setStorageSync存入加密缓存
- 每次使用前用RSA解密
- 定时刷新密钥(比如每24小时)
// 安全获取密钥的示例 async function getSecureKey() { let cachedKey = wx.getStorageSync('encryptedKey') if (!cachedKey) { const { data } = await wx.request({ url: 'https://api.yourservice.com/key', method: 'POST', data: { deviceId: getDeviceId() } }) cachedKey = data.encryptedKey wx.setStorageSync('encryptedKey', cachedKey) } return decryptWithRSA(cachedKey) // RSA解密方法 }对于特别敏感的操作,比如医疗数据加密,我们还会结合微信的云开发能力,把加解密逻辑放在云函数里执行。实测下来,虽然多了网络请求,但安全性提升了好几个等级。有个取巧的办法:可以用小程序的worker来执行加密操作,这样即使主线程被注入恶意代码,worker环境仍是隔离的。