uni-app文件加解密必须先转字符串:读取文件为ArrayBuffer→加密→toString()或Base64编码后存储;解密逆向还原为ArrayBuffer才能正确使用。

uni-app 文件加解密存储必须先转成字符串
uni-app 本身不提供直接对二进制文件(如图片、PDF、Excel)做 AES/SM4 加密后存本地的 API。所有 uni.setStorage、uni.writeFile 等存储接口,只接受 string 或 ArrayBuffer(部分平台),但加密库(如 crypto-js、sm-crypto)输出的密文默认是 CipherParams 对象或 WordArray,不能直接存。
关键动作是:读取文件 → 转为 ArrayBuffer → 加密 → .toString() 或 .toString(CryptoJS.enc.Base64) → 存字符串。解密则逆向:取字符串 → 解密还原为 WordArray → .toString(CryptoJS.enc.Utf8) 或 .sigBytes + .words 转回 ArrayBuffer。
- H5 端用
FileReader.readAsArrayBuffer()读取上传文件;App 和小程序需用uni.chooseMessageFile或uni.chooseImage+uni.getFileSystemManager().readFile获取临时路径再读取二进制 - 加密前务必确认密钥(
key)和初始向量(iv)在跨端时字节长度一致:AES-256 要 32 字节 UTF8 密钥,CBC 模式下iv必须 16 字节,否则 iOS App 会静默失败 - 不要用
CryptoJS.enc.Utf8.parse(fileContent)直接解析二进制 —— 这会导致乱码,必须用CryptoJS.enc.Base64.parse(base64Str)或原生Uint8Array.from(arrayBuffer)构造输入
小程序/APP 端写入加密文件要绕过 base64 编码陷阱
很多开发者把文件转成 Base64 后 AES 加密,再存到 uni.setStorage,结果在微信小程序里解密失败,报 Invalid IV 或空字符串。根本原因是:微信小程序对 setStorage 的 value 做了隐形截断或换行符归一化,而 Base64 字符串若含 \n(比如从 PEM 公钥或某些编码器生成),会被抹掉,导致解密时 IV 错位。
正确做法是:加密后统一用 CryptoJS.enc.Base64.stringify(encrypted.ciphertext) 提取纯 Base64 密文部分(不含 salt、iv 等元信息),再手动拼接 iv(Base64 编码后)作为前缀,例如 ${ivB64}:${cipherB64};解密时按 : 分割,分别 Base64 解码还原 iv 和密文。
- 避免使用
encrypted.toString()(它默认带 salt 和格式头,不同平台解析不一致) - App 端若用
plus.android写入 SD 卡,加密后必须调new java.lang.String(bytes, "UTF-8")转字符串,不能传原始byte[],否则 Android 会丢高位字节 - 微信小程序中,
uni.getFileSystemManager().writeFile接收data类型为string | ArrayBuffer,若传加密后的 Base64 字符串,记得设encoding: 'base64',否则写入的是明文字符而非二进制
解密后还原文件需重建 ArrayBuffer
加密文件最终目的是能被其他应用或系统识别,所以解密后不能只返回字符串,必须还原成标准 ArrayBuffer 才能用于 uni.downloadFile、uni.openDocument 或 Canvas 渲染。
常见错误是:用 CryptoJS.enc.Utf8.stringify(decrypted) 得到字符串再转 TextEncoder().encode() —— 这仅适用于 UTF8 文本,对图片/PDF 会损坏二进制结构。真正安全的方式是拿到 decrypted 的 sigBytes 和 words 数组,逐字构造 Uint8Array:
const words = decrypted.words;
const sigBytes = decrypted.sigBytes;
const u8 = new Uint8Array(sigBytes);
for (let i = 0; i < sigBytes; i++) {
const word = words[i >>> 2];
u8[i] = (word >>> (24 - (i % 4) * 8)) & 0xff;
}
const arrayBuffer = u8.buffer;- 该逻辑必须封装在工具函数中,不能依赖
JSON.stringify或atob,后者对非 ASCII 数据(如图片)不可靠 - 解密后校验
arrayBuffer.byteLength是否与原始文件一致,不一致说明密钥/IV/模式不匹配,立即中断后续操作 - 大文件(>5MB)建议分块加解密,避免内存溢出;uni-app 的 H5 端尤其要注意,iOS Safari 对单次
ArrayBuffer分配有限制
国密 SM4 在文件加密中比 AES 更适合国内合规场景
如果你的应用面向政务、金融或国企客户,单纯 AES 可能不满足等保或密评要求。SM4 是国密标准,且 miniprogram-sm-crypto 已支持全平台(包括 H5、微信/支付宝/百度小程序、App)。但它默认只支持字符串输入,对文件需手动桥接。
流程是:文件 → ArrayBuffer → new Uint8Array(arrayBuffer) → sm4.doEncrypt(uint8Arr, key, { mode: 'cbc', iv }) → 返回 Uint8Array → String.fromCharCode(...) 转字符串存;解密时反向操作,并用 new Uint8Array([...str].map(c => c.charCodeAt(0))) 还原。
- SM4 的 key 和 iv 都必须是 16 字节(128 bit),不能像 AES 那样用 32 字节密钥;用
sm4.generateKey()生成的 hex 字符串需用hexToBytes()转为Uint8Array - H5 端使用 SM4 时,确保构建时已启用
es6支持,否则Uint8Array方法在低版本安卓 WebView 中会报错 - 注意:SM4 加密后数据长度一定是 16 字节对齐,原始文件末尾不足时自动补位,解密后需按原始长度截断,否则打开 PDF 会提示“损坏”
文件加解密不是“套个算法就完事”,核心难点始终在二进制 ↔ 字符串 ↔ ArrayBuffer 的三重转换一致性上。哪怕密钥完全正确,一个平台用了 atob、另一个用了 CryptoJS.enc.Base64.parse,结果就差一个字节,整个文件就废了。动手前先在各端跑通最小闭环:选一张 1KB 的 PNG,加密 → 存 → 取 → 解密 → uni.openDocument 打开,成功了再扩业务逻辑。


















