Java AES/CBC文件加解密异常:前半段明文解密失败
问题分析与解决方案
你遇到的解密前半段乱码问题,核心原因是加密时实际使用的IV和写入文件头的IV不一致,再加上几处流处理的逻辑错误,导致CBC模式下解密的初始块完全错乱,后续内容也受连锁影响。下面具体拆解问题并给出修复方案:
核心问题1:加密时IV使用逻辑错误
当fileStoreIV为true时,你的代码初始化Cipher时没有传入指定的initVector:
if(fileStoreIV) { SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); // 这里未传入指定的IV! }
对于AES/CBC模式,若不主动指定IV,Cipher会自动生成一个随机IV,但你却把自己传入的initVector写到了文件头。解密时用文件头的IV去解密,自然和加密实际用的IV不匹配,导致第一个16字节块(AES块大小)解密乱码,CBC的链式特性还会影响后续块的解密结果。
核心问题2:输入流读取方式错误
加密时你用FileEncryptor.class.getResourceAsStream(loadFile.getName())读取文件,这个方法是从类路径下读取资源,而非读取用户指定的任意路径文件。如果用户输入的文件不在类路径下,这个流会是null,要么读取失败,要么读取到错误的文件。
核心问题3:IV写入时机错误
你在第一次读取明文后才写入IV到文件头:
for(int length=fin.read(bytes); length!=-1; length = fin.read(bytes)){ if(fileStoreIV) { fout.write(initVector); fileStoreIV = false; } cipherOut.write(bytes, 0, length); }
如果文件为空或者第一次读取就到末尾,IV根本不会被写入;而且即使写入,也是在读取了一部分明文之后,逻辑上不符合“IV在文件最开头”的设计。
修复后的加密方法
private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException { // 初始化Cipher:无论是否存储IV,都使用指定的initVector Cipher cipher = Cipher.getInstance(CIPHER); IvParameterSpec iv = new IvParameterSpec(initVector); SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); // 文件检查逻辑保持不变 Path loadFilePath = Paths.get(inputFile); if (!Files.exists(loadFilePath)){ System.out.println("The inputFile you specified does not exist"); return; } Path saveFile = Paths.get(outputFile); Path parentDir = saveFile.getParent(); if (parentDir != null && !Files.exists(parentDir)) { System.out.println("The outputFile directory/s you specified does not exist"); return; } System.out.println("Secret key is " + Base64.getEncoder().encodeToString(key)); System.out.println("IV is " + Base64.getEncoder().encodeToString(initVector)); // 修正流处理逻辑:先写IV,再处理加密流 try (InputStream fin = Files.newInputStream(loadFilePath); // 替换为正确的文件输入流 OutputStream fout = Files.newOutputStream(saveFile)) { // 先把IV写入文件头部 if(fileStoreIV) { fout.write(initVector); } // 初始化CipherOutputStream,开始加密写入 try (CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher)) { final byte[] bytes = new byte[1024]; int length; while ((length = fin.read(bytes)) != -1) { cipherOut.write(bytes, 0, length); } } } catch (IOException e) { System.out.println("Something went wrong with reading and writing these files!"); System.out.println("Please check you have the latest version of this program"); System.out.println("Contact your IT admin to make sure you have sufficient privileges"); } System.out.println("SUCCESS! Encryption finished, saved at specified location"); }
修复后的解密方法
还要修复解密时读取IV的逻辑,确保完整读取16字节IV:
private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidKeyException, InvalidAlgorithmParameterException { Cipher cipher = Cipher.getInstance(CIPHER); SecretKeySpec skeySpec = new SecretKeySpec(Base64.getDecoder().decode(inputKEY), ALGORITHM); Path loadFilePath = Paths.get(inputFile); if (!Files.exists(loadFilePath)){ System.out.println("The inputFile you specified does not exist"); return; } Path saveFile = Paths.get(outputFile); Path parentDir = saveFile.getParent(); if (parentDir != null && !Files.exists(parentDir)) { System.out.println("The outputFile directory/s you specified does not exist"); return; } try (InputStream encryptedData = Files.newInputStream(loadFilePath)) { IvParameterSpec ivSpec; if(fileStoreIV) { // 确保完整读取16字节IV byte[] fileIV = new byte[16]; int bytesRead = 0; while (bytesRead < 16) { int read = encryptedData.read(fileIV, bytesRead, 16 - bytesRead); if (read == -1) { throw new IOException("Invalid encrypted file: IV missing or incomplete"); } bytesRead += read; } System.out.println(Base64.getEncoder().encodeToString(fileIV)); ivSpec = new IvParameterSpec(fileIV); } else { ivSpec = new IvParameterSpec(Base64.getDecoder().decode(inputIV)); } // 初始化解密Cipher cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec); // 解密写入文件 try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher); OutputStream decryptedOut = Files.newOutputStream(saveFile)) { final byte[] bytes = new byte[1024]; int length; while ((length = decryptStream.read(bytes)) != -1) { decryptedOut.write(bytes, 0, length); } } } catch (IOException e) { System.out.println("Something went wrong with reading and writing these files!"); System.out.println("Please check you have the latest version of this program"); System.out.println("Contact your IT admin to make sure you have sufficient privileges"); } System.out.println("SUCCESS! Decryption finished, saved at specified location"); }
关键修改说明
- 加密时强制使用指定的IV:无论
fileStoreIV是否为true,都用传入的initVector初始化Cipher,保证加密用的IV和写入文件头的IV完全一致。 - 替换输入流为文件流:用
Files.newInputStream读取用户指定的文件,避免类路径限制导致的读取错误。 - 提前写入IV:在读取明文前就把IV写入文件头部,保证文件结构符合设计预期。
- 解密时完整读取IV:循环读取直到拿到16字节IV,避免因流读取不完整导致IV错误。
这样修改后,加解密的IV就能完全匹配,流处理逻辑也正确,乱码问题应该就能解决了。
内容的提问来源于stack exchange,提问作者CHOCO BLOCK
相关产品推荐
相关产品推荐

