使用AES GCM模式的CipherOutputStream时遭遇ShortBufferException
AES/GCM解密抛出ShortBufferException的问题解决
核心错误点
你的代码里有两个致命问题导致了这个异常:
1. 加密时未关闭CipherOutputStream,丢失GCM认证标签
AES/GCM加密完成后必须写入认证标签,解密时要靠它验证数据完整性。CipherOutputStream只有在调用close()时才会触发Cipher.doFinal(),把标签写入文件。你之前写完加密数据直接结束,没关流,导致文件缺少标签,解密时GCM模式无法完成验证,直接抛出异常。
2. 解密时重复创建FileInputStream,把nonce当成加密数据解密
你读取完nonce后,重新new了一个FileInputStream,等于让解密流从头开始读取——把前面12字节的nonce也当成加密数据处理,数据结构完全错误,触发缓冲区异常。
修正后的完整代码
import javax.crypto.*; import javax.crypto.spec.GCMParameterSpec; import java.io.*; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.HashMap; public class FileEncrypterDecrypter { private static final int ALGORITHM_NONCE_SIZE = 12; private static final String ALGORITHM_NAME = "AES/GCM/NoPadding"; private final static int ALGORITHM_TAG_SIZE = 128; private final static HashMap<String, SecretKey> fileToKey = new HashMap<>(); FileEncrypterDecrypter() {} public static void encrypt(InputStream plaintext, String filePath) throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException { SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey(); fileToKey.put(filePath, secretKey); SecureRandom rand = new SecureRandom(); byte[] nonce = new byte[ALGORITHM_NONCE_SIZE]; rand.nextBytes(nonce); System.out.println(Arrays.toString(nonce)); Cipher cipher = Cipher.getInstance(ALGORITHM_NAME); cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(ALGORITHM_TAG_SIZE, nonce)); // 用try-with-resources自动关流,确保CipherOutputStream写完认证标签 try (FileOutputStream fileOut = new FileOutputStream(filePath); CipherOutputStream cipherOut = new CipherOutputStream(fileOut, cipher)) { fileOut.write(nonce); plaintext.transferTo(cipherOut); } } public static String decrypt(String filePath) throws InvalidKeyException, IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { SecretKey secretKey = fileToKey.get(filePath); if (secretKey == null) { throw new IllegalArgumentException("找不到该文件对应的密钥"); } try (FileInputStream fileIn = new FileInputStream(filePath)) { byte[] nonce = new byte[ALGORITHM_NONCE_SIZE]; int readBytes = fileIn.read(nonce); if (readBytes != ALGORITHM_NONCE_SIZE) { throw new IOException("读取nonce不完整,文件可能损坏"); } System.out.println(Arrays.toString(nonce)); Cipher cipher = Cipher.getInstance(ALGORITHM_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(ALGORITHM_TAG_SIZE, nonce)); // 直接用已经跳过nonce的fileIn,不用重新开流 try (CipherInputStream cipherIn = new CipherInputStream(fileIn, cipher); InputStreamReader inputReader = new InputStreamReader(cipherIn); BufferedReader reader = new BufferedReader(inputReader)) { StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } } } }
额外优化说明
- 用
try-with-resources自动管理流,无需手动关闭,既避免资源泄漏,又保证CipherOutputStream能正确写入认证标签。 - 添加nonce读取完整性检查,防止文件损坏时出现隐式错误。
- 添加密钥存在性判断,避免空指针异常。
测试验证
你的主方法现在可以正常运行,解密后会输出HELLO WORLD,不会再抛出异常。
内容的提问来源于stack exchange,提问作者khh
相关产品推荐
相关产品推荐

