使用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
相关产品推荐
相关产品推荐

