You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用node-forge AES-CBC解密文件失败,请求技术支持

问题排查与修复方案

我帮你排查了代码里的几个关键问题,这些应该就是导致解密后文件异常的核心原因:

1. 废弃的readAsBinaryString引发的编码破坏

readAsBinaryString已经被浏览器废弃,它会把二进制数据转换成UTF-8字符串,这会破坏原始字节(尤其是非ASCII范围的字节),直接导致哈希校验、IV读取、解密过程都出现数据失真。必须改用readAsArrayBuffer来处理所有二进制数据,它能完整保留原始字节信息。

2. IV的格式不符合forge要求

你当前把IV读取成字符串后直接传给解密函数,但node-forge的AES-CBC解密要求IV是字节数组或者forge的Buffer对象,字符串格式的IV会导致解密算法无法正确解析,最终输出错误的解密结果。

3. 解密后数据的转换错误

cipher.output是forge的Buffer对象,直接取.data得到的是字符串,用这个字符串创建File时会默认用UTF-8编码,破坏原始二进制内容。需要把forge Buffer转换成Uint8Array后再创建文件。

4. 哈希校验的逻辑错误

同样因为用了readAsBinaryString,哈希计算的是字符串的字节,而不是原始文件的字节,即使服务器返回的哈希正确,也可能出现校验不匹配(或者侥幸匹配但后续解密出错)。


修正后的完整代码

// 获取文件
const xhr = new XMLHttpRequest();
xhr.open("GET", config.urlLogin + "files/" + e.id, true);
xhr.responseType = "arraybuffer";
xhr.send();
xhr.onreadystatechange = () => {
  if (xhr.readyState !== 4) return;
  if (xhr.status !== 200) {
    console.log(xhr.status + ": " + xhr.statusText);
    return;
  }
  const file = new File([xhr.response], e.name);
  this.checkHash(file, (validatedFile) => {
    if (validatedFile) {
      this.decryptFile(validatedFile);
    } else {
      console.log("哈希校验失败");
    }
  });
};

// 校验哈希(改用ArrayBuffer处理)
checkHash(file, callback) {
  const reader = new FileReader();
  const expectedHash = this.state.fileChoose.hash;
  reader.onload = (event) => {
    const arrayBuffer = event.target.result;
    // 用forge处理ArrayBuffer
    const buffer = forge.util.createBuffer(new Uint8Array(arrayBuffer));
    const md = forge.md.sha256.create();
    md.update(buffer.getBytes());
    const computedHash = window.btoa(md.digest().data);
    if (expectedHash === computedHash) {
      callback(file);
    } else {
      callback(false);
    }
  };
  reader.readAsArrayBuffer(file);
}

// 合并解密逻辑(直接从ArrayBuffer处理IV和加密内容)
decryptFile(file) {
  const reader = new FileReader();
  const { key, name } = this.state.fileChoose;
  reader.onload = (event) => {
    const arrayBuffer = event.target.result;
    // 提取前16字节作为IV,剩下的是加密内容
    const ivBytes = new Uint8Array(arrayBuffer.slice(0, 16));
    const encryptedBytes = new Uint8Array(arrayBuffer.slice(16));

    // 初始化forge解密器
    const decipher = forge.cipher.createDecipher("AES-CBC", window.atob(key));
    decipher.start({ iv: ivBytes });
    // 把加密字节转成forge Buffer
    decipher.update(forge.util.createBuffer(encryptedBytes));
    const decryptSuccess = decipher.finish();

    if (!decryptSuccess) {
      console.log("解密失败,可能是密钥或IV错误");
      return;
    }

    // 把forge Buffer转成Uint8Array,再创建文件
    const decryptedBuffer = decipher.output;
    const decryptedBytes = new Uint8Array(decryptedBuffer.length());
    for (let i = 0; i < decryptedBuffer.length(); i++) {
      decryptedBytes[i] = decryptedBuffer.getByte(i);
    }

    const finishFile = new File([decryptedBytes], name);
    const link = document.createElement("a");
    link.download = name;
    link.href = URL.createObjectURL(finishFile);
    link.click();
    // 释放URL对象,避免内存泄漏
    URL.revokeObjectURL(link.href);
  };
  reader.readAsArrayBuffer(file);
}

关键修复点说明

  • 全程用readAsArrayBuffer处理二进制数据,避免编码失真
  • 直接从ArrayBuffer切片提取IV和加密内容,保证字节完整性
  • 用Uint8Array作为IV传给forge解密器,符合格式要求
  • 解密后把forge Buffer转成Uint8Array再创建文件,确保原始字节不被编码破坏
  • 增加了解密失败的判断逻辑,方便排查问题
  • 增加了URL.revokeObjectURL释放资源,避免内存泄漏

内容的提问来源于stack exchange,提问作者Vadim Baranov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:47:27