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

JavaScript浏览器端如何分块解密1GB级AES-CTR加密大文件

浏览器端1GB级大文件AES-CTR分块解密实现

现有代码的核心问题

  • 计数器计算存在精度溢出:JS原生Number类型为64位双精度浮点数,安全整数上限仅为2^53-1,现有实现中byteArrayToLong/longToByteArray把16字节计数器转成Number计算,大文件分块到后期计数器值超过安全整数范围会出现精度丢失,直接导致计数器值错误,解密结果乱码。
  • length参数配置错误:Web Crypto API中AES-CTR配置项的length字段指代计数器在16字节counter块中占用的比特长度,不是分块大小。常规AES-CTR实现多采用12字节nonce + 4字节计数器的组合,对应length值为32,传入128属于完全错误的参数。
  • 分块策略不合理:128字节的分块过小,1GB文件会产生数百万次解密API调用,上下文开销极高,解密速度极慢;同时把所有解密块存在内存中最后拼接,全程内存占用会达到文件大小的2倍以上,很容易触发浏览器内存上限导致页面崩溃。
  • 边界逻辑冗余:do-while循环结束后额外多写了一次解密逻辑,会重复解密末尾块,甚至出现越界读取的问题。

实现思路

  • 分块大小调整到1MB~10MB区间,平衡API调用开销和单次操作内存占用,不要使用百字节级的极小分块。
  • 不要把16字节计数器转成Number类型计算,直接维护Uint8Array格式的计数器缓冲区,每处理完一个块直接对缓冲区做字节级进位加法,从根本上规避整数精度溢出问题。注意计数器字节序必须和加密端完全匹配,大端/小端错配会直接导致解密失败。
  • 解密过程不要把所有结果块暂存在内存中等待最终拼接,每解密完一个块就直接写入输出流,把内存占用控制在单块大小级别,避免大文件导致内存溢出。
  • 所有AES参数(初始nonce值、计数器长度、字节序)必须和加密端完全对齐,否则无法得到正确解密结果。

参考实现代码

const AES_BLOCK_SIZE = 16;
/**
 * 大文件AES-CTR分块解密
 * @param {CryptoKey} subtleKey 预导入的AES密钥
 * @param {Uint8Array} subtleIv 16字节初始IV/nonce
 * @param {File|Blob} encryptedFile 待解密文件
 * @param {number} counterBitLength 计数器占用比特长度,常规为32(即后4字节为计数器)
 * @param {number} chunkSize 单块大小,默认4MB
 * @returns {Promise<Blob>} 解密后的文件Blob
 */
async function decryptLargeFile(subtleKey, subtleIv, encryptedFile, counterBitLength = 32, chunkSize = 4 * 1024 * 1024) {
    if (subtleIv.byteLength !== 16) throw new Error('AES-CTR初始IV长度必须为16字节');
    const totalSize = encryptedFile.size;
    const decryptedParts = [];
    // 复制初始IV作为当前计数器值,避免修改原始IV
    const currentCounter = new Uint8Array(subtleIv);
    let offset = 0;

    while (offset < totalSize) {
        const readEnd = Math.min(offset + chunkSize, totalSize);
        const chunkBuffer = await encryptedFile.slice(offset, readEnd).arrayBuffer();
        const decrypted = await window.crypto.subtle.decrypt(
            {
                name: "AES-CTR",
                counter: currentCounter,
                length: counterBitLength
            },
            subtleKey,
            chunkBuffer
        );
        decryptedParts.push(new Uint8Array(decrypted));
        // 计算当前块对应的AES块数量,累加计数器
        const processedBlocks = Math.ceil((readEnd - offset) / AES_BLOCK_SIZE);
        let carry = processedBlocks;
        // 字节级进位累加(默认大端序,计数器在块末尾,从最后一个字节往前遍历累加;如果是小端序则从第一个字节往后遍历)
        for (let i = currentCounter.length - 1; i >= 0 && carry > 0; i--) {
            const sum = currentCounter[i] + carry;
            currentCounter[i] = sum & 0xff;
            carry = Math.floor(sum / 256);
        }
        if (carry > 0) console.warn('AES-CTR计数器溢出,请检查加密参数配置');
        offset = readEnd;
    }
    return new Blob(decryptedParts);
}

提示:如果要处理1GB以上的超大文件,可以搭配浏览器本地文件系统写入能力,每解密完一个块就直接写入磁盘,不需要暂存所有解密分片,内存占用可以稳定在单块大小水平,不会出现页面崩溃。如果加密端使用的计数器字节序、nonce和计数器拆分规则和常规实现不同,对应调整计数器累加的遍历逻辑即可。

内容的提问来源于stack exchange,提问作者Nino Zalascek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:12:22