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

使用CipherInputStream加密文件:如何预判read()返回-1以实现进度条?

关于CipherInputStream加密的进度条实现与密文长度问题

首先:CipherInputStream加密后的密文长度和原文件相同吗?

短答案:通常不相同,这完全取决于你使用的加密模式和填充方式:

  • 带填充的块加密模式(比如CBC/PKCS5Padding):分组加密算法(如AES)要求输入必须是块大小的整数倍,所以会自动填充数据到块大小的倍数。比如AES块大小是16字节,原文件17字节的话,密文会是32字节。
  • 无填充的块加密模式(比如CBC/NoPadding):只有当原文件长度刚好是块大小的整数倍时才能正常工作,此时密文长度和原文件完全一致;否则加密会直接抛出异常。
  • 流加密模式(比如CTR、OFB、CFB):这类模式不需要填充,密文长度和原文件完全相同,因为是逐字节进行加密操作。
  • AEAD认证加密模式(比如GCM、CCM):除了加密后的明文部分(长度遵循上述块/流模式规则),还会附加一段认证标签(通常16字节),所以密文总长度会比原文件(或填充后的长度)多出标签的长度。

然后:怎么预判CipherInputStream的读取结束时机,实现进度条?

CipherInputStream的read()方法返回-1就是读取结束,但由于密文长度和原文件可能不同,直接用原文件长度做进度会不准。正确的做法是先计算出加密后的密文总长度,再根据已读取的字节数来更新进度:

步骤1:计算密文总长度

你可以基于原文件长度和Cipher实例的配置(模式、填充、是否是AEAD)来提前算出密文的总长度。这里给你一段实用的Java代码片段:

File inputFile = new File("your-input-file-path");
long originalFileLength = inputFile.length();
Cipher cipher = ...; // 注意:这里要确保Cipher已经初始化完成(调用过init())
long cipherTextTotalLength;

String transformation = cipher.getTransformation();
int blockSize = cipher.getBlockSize();

if (transformation.contains("GCM") || transformation.contains("CCM")) {
    // 处理AEAD模式:填充后的明文长度 + 认证标签长度
    long paddedPlaintextLength = ((originalFileLength + blockSize - 1) / blockSize) * blockSize;
    GCMParameterSpec spec = cipher.getParameterSpec(GCMParameterSpec.class);
    int tagLengthBytes = spec.getTLen() / 8; // 转换为字节数(TLen是比特数)
    cipherTextTotalLength = paddedPlaintextLength + tagLengthBytes;
} else if (transformation.contains("NoPadding")) {
    // 无填充模式:必须确保原长度是块大小的倍数,否则加密会失败
    if (originalFileLength % blockSize != 0) {
        throw new IllegalArgumentException("Input length must be a multiple of block size for NoPadding");
    }
    cipherTextTotalLength = originalFileLength;
} else if (transformation.contains("CTR") || transformation.contains("OFB") || transformation.contains("CFB")) {
    // 流模式:密文长度等于原文件长度
    cipherTextTotalLength = originalFileLength;
} else {
    // 默认处理带填充的块模式(如CBC/PKCS5Padding)
    cipherTextTotalLength = ((originalFileLength + blockSize - 1) / blockSize) * blockSize;
}

步骤2:读取时更新进度条

拿到总长度后,就可以在读取循环中累计已读取的字节数,计算进度百分比:

byte[] buffer = new byte[8192]; // 8KB缓冲,可根据需求调整
long bytesReadTotal = 0;
int bytesRead;

try (CipherInputStream cis = new CipherInputStream(new FileInputStream(inputFile), cipher);
     FileOutputStream fos = new FileOutputStream("your-output-file-path")) {

    while ((bytesRead = cis.read(buffer)) != -1) {
        fos.write(buffer, 0, bytesRead);
        bytesReadTotal += bytesRead;
        
        // 计算并更新进度
        int progressPercent = (int) ((bytesReadTotal * 100) / cipherTextTotalLength);
        // 这里可以替换成你的UI更新逻辑,比如Swing的ProgressBar或者控制台打印
        System.out.printf("Encryption Progress: %d%%\r", progressPercent);
    }
} catch (IOException e) {
    e.printStackTrace();
}

注意事项

  • 如果你加密的不是本地文件(比如网络流),无法提前获取原长度,那只能做“无限滚动”的进度条,或者根据预估的流量来处理,但文件加密场景下这个方法完全适用。
  • 确保Cipher实例已经完成初始化(调用过init(Cipher.ENCRYPT_MODE, key, params)),否则getBlockSize()或getParameterSpec()可能返回错误值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 14:12:47