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

如何在浏览器中流式解密从IPFS获取的AES-256-CTR加密文件?

浏览器中AES-256-CTR流式解密IPFS文件方案

问题背景

我通过Node.js将本地文件流式加密后上传到IPFS,加密代码如下:

const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-ctr', key, iv, null);
const input = fs.createReadStream(inputFilePath);
const storage = makeStorageClient();
const keyString = new Int32Array(key).toString();
const cid = await storage.put([{ name: fileName, stream: () => input.pipe(cipher) }]);

现在需要在浏览器中逆向操作:从IPFS获取文件(已转为ArrayBuffer),流式解密后保存到本地。浏览器无法使用Node.js的crypto模块,原本想这么实现但行不通:

const retrieved = await ipfs.cat(cid+'/'+link.name);
const decipher = crypto.createDecipheriv('aes-256-ctr', key, iv, null);
const input = new ReadableStream(retrieved);
const output = streamSaver.createWriteStream(link.name);
const finished = await input.pipe(decipher).pipe(output);

尝试用浏览器的SubtleCrypto.decrypt,但AES-256-CTR需要传入包含counter和length的参数对象,不知道怎么获取这两个参数,同时想找更优的浏览器流式解密方案。


解决方案

1. AES-256-CTR参数对应关系

  • counter:就是你加密时生成的iv。Node.js的aes-256-ctr模式中,传入的iv本质就是CTR模式的初始计数器,两者完全等价,直接把加密时的iv转成ArrayBuffer传入即可。
  • length:计数器的比特长度,Node.js的aes-256-ctr默认使用128位(16字节)的计数器,所以这里填128就行。

注意:加密时的iv必须和解密时一致,所以你需要把iv和CID、密钥一起保存(比如作为IPFS文件的元数据,或者单独存储),否则无法完成解密。

2. 浏览器流式解密实现

SubtleCrypto.decrypt本身不支持流式处理,但可以通过TransformStream逐段处理数据流,结合CTR模式的特性(只要计数器连续递增,分段解密不影响结果)来实现流式解密。具体步骤和代码如下:

步骤1:准备密钥和IV

先把Node.js中使用的密钥(Buffer类型)转换为浏览器的CryptoKey对象,同时把IV转为ArrayBuffer:

// 假设key是从外部获取的Buffer/ArrayBuffer,iv是加密时保存的Buffer/ArrayBuffer
async function getCryptoKey(rawKey) {
  return await window.crypto.subtle.importKey(
    'raw',
    rawKey,
    { name: 'AES-CTR' },
    false,
    ['decrypt']
  );
}

const cryptoKey = await getCryptoKey(key);
const counterBuffer = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength); // 转成ArrayBuffer

步骤2:实现流式解密的TransformStream

function createDecryptTransform(cryptoKey, initialCounter) {
  let counter = new DataView(initialCounter.slice(0)); // 复制初始计数器,避免修改原数据
  let counterIncremented = false;

  return new TransformStream({
    async transform(chunk, controller) {
      // CTR模式下,每处理一段数据后需要递增计数器
      if (counterIncremented) {
        // 递增计数器(大端序)
        let carry = 1;
        for (let i = counter.byteLength - 1; i >= 0 && carry; i--) {
          const val = counter.getUint8(i) + carry;
          counter.setUint8(i, val & 0xff);
          carry = val >> 8;
        }
      }
      counterIncremented = true;

      // 构建解密参数
      const params = {
        name: 'AES-CTR',
        counter: counter,
        length: 128
      };

      // 解密当前段
      const decryptedChunk = await window.crypto.subtle.decrypt(params, cryptoKey, chunk);
      controller.enqueue(new Uint8Array(decryptedChunk));
    }
  });
}

步骤3:IPFS数据流处理+保存到本地

async function decryptAndSaveFromIPFS(cid, fileName, cryptoKey, iv) {
  // 从IPFS获取数据流(ipfs.cat返回AsyncIterable,转成ReadableStream)
  const ipfsStream = await ipfs.cat(`${cid}/${fileName}`);
  const readableStream = new ReadableStream({
    async start(controller) {
      for await (const chunk of ipfsStream) {
        controller.enqueue(chunk);
      }
      controller.close();
    }
  });

  // 创建解密转换流
  const decryptStream = createDecryptTransform(cryptoKey, iv);
  // 创建保存文件的流(使用StreamSaver)
  const writeStream = streamSaver.createWriteStream(fileName);

  // 流式拼接处理
  await readableStream.pipeThrough(decryptStream).pipeTo(writeStream);
}

// 调用示例
await decryptAndSaveFromIPFS(cid, link.name, cryptoKey, counterBuffer);

3. 优化建议

  • 使用现成库简化开发:如果不想手动处理计数器递增,可以用web-streams-polyfill或者crypto-browserify这类库,它们封装了流式加密/解密的逻辑,减少手动实现的出错概率。
  • 元数据存储:加密时建议把iv、密钥哈希(用于验证)等元数据和文件一起上传到IPFS(比如打包成一个目录),方便解密时直接获取。

内容的提问来源于stack exchange,提问作者Boris K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 14:35:45