HTML5本身不提供加密上传能力,所谓“HTML5加密上传”实为前端用Web Crypto API(如AES-GCM)在上传前对文件加密,再通过fetch等发送密文及IV、salt、authTag等元数据。

HTML5 本身不提供加密上传能力
HTML5 标准里没有 encryptFile()、cryptoUpload() 这类 API, 拿到的 File 或 Blob 是明文二进制数据,浏览器不会自动加密。所谓“HTML5 加密上传”,本质是:前端用 JavaScript 在上传前对文件内容做加密(如 AES),再把密文通过 fetch 或 XMLHttpRequest 发给后端。
用 Web Crypto API 在浏览器端 AES 加密文件
现代浏览器支持 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 - 避免把密码或密钥明文塞进 URL 或 header,易被日志/代理截获
常见翻车点:跨域、MIME、解密失败
即使前端加密逻辑正确,上传仍可能失败:
立即学习“前端免费学习笔记(深入)”;
- CORS 预检失败:后端
Access-Control-Allow-Headers必须包含自定义字段名(如X-Enc-IV),或统一走FormData避免预检 - 后端收到的文件体是空:没调用
formData.append("encrypted_file", new Blob([encrypted])),而是 append 了ArrayBuffer - 解密报 “data not authentic”:IV 或 authTag 传输中被截断/换行/转义(尤其 Base64 后没 strip \n)
- Chrome 扩展或某些企业代理会劫持
fetch请求并修改 body,导致密文损坏——建议加简单校验(如上传前计算密文 SHA-256 并随请求发送)
加密不是银弹。如果服务器本身不可信,或用户设备已中毒,前端加密意义有限。重点其实是明确威胁模型:你防的是传输窃听?还是中间存储泄露?还是运维人员越权访问?每种场景对应不同设计取舍。











