CipherOutputStream异常求助:文件接收不完整及数组越界报错
问题分析与解决方案
首先,你的问题根源在于加密流的使用方式错误以及客户端与服务端的加密解密流程不匹配,具体原因和解决方法如下:
核心问题1:CipherOutputStream未正确关闭,导致加密数据不完整
AES属于块加密算法,当待加密的数据长度不是加密块大小(128位,即16字节)的整数倍时,会自动进行填充操作。CipherOutputStream.flush()只会刷新当前缓冲区的内容,但不会处理最后的填充块——只有调用close()时,才会触发填充并输出剩余的加密数据。
你现在只调用了cipherOutputStream.flush(),导致最后一部分加密数据留在缓冲区没有发送到服务端:
- 单文件发送时,服务端接收的字节数不足,读取到流末尾时
count=-1,此时调用bufferedOutputStream.write(buffer, 0, count)就会触发ArrayIndexOutOfBoundsException(因为count为负数)。 - 多文件发送时,前几个文件的后续操作可能间接触发了缓冲区刷新,但最后一个文件没有后续操作,导致数据丢失。
解决方法:写完文件内容后,必须调用cipherOutputStream.close(),而不是仅flush:
bufferedInputStream.close(); cipherOutputStream.close(); // 触发最后填充块的输出,并刷新所有缓冲区
如果不想关闭底层的ObjectOutputStream(还要用它发送其他数据),可以使用Java 9+提供的带leaveOpen参数的构造器:
CipherOutputStream cipherOutputStream = new CipherOutputStream(objectOutputStream, cipherEncrypt, true); // 此时close()只会关闭CipherOutputStream,不会影响底层的objectOutputStream
核心问题2:服务端未对应使用解密流,且缺少密钥
你客户端用CipherOutputStream加密了文件内容,但服务端直接用ObjectInputStream读取原始字节——这不仅无法还原出原始文件,还会因为数据格式不匹配导致读取异常。更关键的是:你客户端生成了AES密钥,但没有把这个密钥发送给服务端,服务端根本没有解密的依据!
解决方法:
- 客户端先发送密钥:因为
SecretKey实现了Serializable,可以直接通过ObjectOutputStream发送:
// 生成密钥后立即发送给服务端 SecretKey secretKeyEncrypt = keyGenerator.generateKey(); objectOutputStream.writeObject(secretKeyEncrypt); objectOutputStream.flush(); // 确保密钥先发送到位 // 再创建CipherOutputStream加密文件内容 Cipher cipherEncrypt = Cipher.getInstance("AES"); cipherEncrypt.init(Cipher.ENCRYPT_MODE, secretKeyEncrypt); CipherOutputStream cipherOutputStream = new CipherOutputStream(objectOutputStream, cipherEncrypt);
- 服务端用CipherInputStream解密:先读取密钥,再创建解密流读取加密数据:
while(i < receivingClientFiles) { // 先读取客户端发送的密钥 SecretKey secretKeyDecrypt = (SecretKey) objectInputStream.readObject(); Cipher cipherDecrypt = Cipher.getInstance("AES"); cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeyDecrypt); CipherInputStream cipherInputStream = new CipherInputStream(objectInputStream, cipherDecrypt); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(diretorio + "/" + fileName)); byte[] buffer = new byte[1024]; int count = 0; // 注意:加密后的文件长度≠原始文件长度(因为填充),所以不能用fileLength判断结束 while ((count = cipherInputStream.read(buffer)) != -1) { bufferedOutputStream.write(buffer, 0, count); } bufferedOutputStream.close(); cipherInputStream.close(); i++; }
额外注意事项
- 加密模式与填充一致性:确保客户端和服务端使用完全相同的加密参数,默认
Cipher.getInstance("AES")对应AES/ECB/PKCS5Padding,如果需要更安全的模式(比如CBC),需要明确指定并传递IV向量(同样要发送给服务端)。 - 流的资源管理:建议使用try-with-resources语句自动管理流的关闭,避免资源泄漏:
// 客户端示例 try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(sendingClientFile)); CipherOutputStream cipherOutputStream = new CipherOutputStream(objectOutputStream, cipherEncrypt)) { int count = 0; while ((count = bufferedInputStream.read(buffer, 0, buffer.length)) != -1) { cipherOutputStream.write(buffer, 0, count); } } // 自动关闭流,触发填充和刷新
内容的提问来源于stack exchange,提问作者Miguel
相关产品推荐
相关产品推荐

