HTML5本身不提供加密上传能力,所谓“HTML5加密上传”实为前端用Web Crypto API(如AES-GCM)在上传前对文件加密,再通过fetch等发送密文及IV、salt、authTag等元数据。
HTML5 标准里没有 encryptFile()、cryptoUpload() 这类 API, 拿到的 File 或 Blob 是明文二进制数据,浏览器不会自动加密。所谓“HTML5 加密上传”,本质是:前端用 JavaScript 在上传前对文件内容做加密(如 AES),再把密文通过 fetch 或 XMLHttpRequest 发给后端。
现代浏览器支持 window.crypto.subtle,可安全生成密钥、执行 AES-GCM(推荐,带认证)。注意:密钥不能硬编码,也不能从服务端直接下发明文密钥——应由用户输入派生(如 PBKDF2 + password)。
AES-GCM 而非 AES-CBC:GCM 自带完整性校验,防篡改importKey() 的格式要设为 "raw",否则解密失败iv、authTag 和密文一起上传,后端缺一不可File.slice() 分块 + stream().getReader() 流式加密更稳妥const encoder = new TextEncoder(); const password = "user-input-passphrase"; const salt = crypto.getRandomValues(new Uint8Array(16)); const key = await crypto.subtle.importKey( "raw", await crypto.subtle.deriveKey( { name: "PBKDF2", salt, iterations: 100_000, hash: "SHA-256" }, await crypto.subtle.importKey("raw", encoder.encode(password), { name: "PBKDF2" }, false, ["deriveKey"]), { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ), { name: "AES-GCM" }, false, ["encrypt"] ); const iv = crypto.getRandomValues(new Uint8Array(12)); const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, await file.arrayBuffer());
后端需要知道用了什么算法、IV、盐值、认证标签才能解密。不能拼接在文件头(破坏 MIME 类型),推荐用 FormData 多字段提交:
encrypted_file:二进制密文(Blob 或 ArrayBuffer)iv:Base64 编码的 IV(btoa(String.fromCharCode(...iv)))salt:同上,用于重算密钥auth_tag:GCM 的 tag 字段,同样 Base64即使前端加密逻辑正确,上传仍可能失败:
Access-Control-Allow-Headers 必须包含自定义字段名(如 X-Enc-IV),或统一走 FormData 避免预检formData.append("encrypted_file", new Blob([encrypted])),而是 append 了 ArrayBuffer
fetch 请求并修改 body,导致密文损坏——建议加简单校验(如上传前计算密文 SHA-256 并随请求发送)加密不是银弹。如果服务器本身不可信,或用户设备已中毒,前端加密意义有限。重点其实是明确威胁模型:你防的是传输窃听?还是中间存储泄露?还是运维人员越权访问?每种场景对应不同设计取舍。