Java基于密码的文件解密报错:缺失IV参数异常
解密时抛出
InvalidAlgorithmParameterException: Missing parameter type: IV expected的解决办法 问题描述
编写了一个基于密码的文件加密程序,使用PBEWithHmacSHA256AndAES_256算法,加密时将salt存入文件,加密流程正常,但解密时抛出异常:
java.security.InvalidAlgorithmParameterException: Missing parameter type: IV expected.
程序调用格式:enc "password" fileToEncrypt.txt destinationFile.enc
原因分析
- 未保存加密时自动生成的IV:
PBEWithHmacSHA256AndAES_256底层采用AES-CBC模式,加密时Cipher会自动生成随机IV,但代码仅保存了salt,未将IV写入加密文件。解密时算法需要该IV才能正确初始化,因此抛出异常。 - 解密时流初始化顺序错误:先创建了
CipherInputStream,之后才初始化Cipher,导致Cipher处于未初始化状态就被使用,进一步加剧问题。
修复步骤
1. 修改加密逻辑,保存IV
加密时获取Cipher生成的IV,将salt和IV依次写入加密文件(salt在前,IV在后)。
2. 修改解密逻辑,读取并使用IV
解密时先读取salt和IV,用这两个参数正确初始化Cipher后,再创建CipherInputStream处理加密内容。
修复后的完整代码
import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.*; import java.util.logging.Logger; public class FileEncryptor { private static final Logger LOG = Logger.getLogger(FileEncryptor.class.getSimpleName()); private static final String CIPHER_ALGORITHM = "PBEWithHmacSHA256AndAES_256"; private static final int SALT_LENGTH = 16; private static final int IV_LENGTH = 16; // AES-CBC的IV固定为16字节 private static final int ITERATION_COUNT = 1000; public static void main(String[] args) { if (args.length != 4) { errorHandle("请使用4个参数调用程序:[enc/dec] \"password\" 输入文件 输出文件"); return; } String mode = args[0]; if (!mode.equals("enc") && !mode.equals("dec")) { errorHandle("请指定enc(加密)或dec(解密)模式"); return; } try { if (mode.equals("enc")) { SecureRandom sr = new SecureRandom(); byte[] salt = new byte[SALT_LENGTH]; sr.nextBytes(salt); char[] password = args[1].toCharArray(); PBEKeySpec pbeKeySpec = new PBEKeySpec(password); SecretKeyFactory keyFac = SecretKeyFactory.getInstance(CIPHER_ALGORITHM); SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, ITERATION_COUNT); cipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec); byte[] iv = cipher.getIV(); // 获取自动生成的IV if (encrypt(cipher, args[2], args[3], salt, iv)) { LOG.info("加密完成,结果保存至:" + args[3]); } } else { char[] password = args[1].toCharArray(); PBEKeySpec pbeKeySpec = new PBEKeySpec(password); SecretKeyFactory keyFac = SecretKeyFactory.getInstance(CIPHER_ALGORITHM); SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); if (decrypt(cipher, pbeKey, args[2], args[3])) { LOG.info("解密完成,结果保存至:" + args[3]); } } } catch (Exception e) { exceptionHandle(e); } } public static boolean encrypt(Cipher cipher, String input, String output, byte[] salt, byte[] iv) { LOG.info("待加密文件:" + input); try (FileInputStream in = new FileInputStream(input); FileOutputStream out = new FileOutputStream(output); CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher)) { // 先写入salt,再写入IV out.write(salt); out.write(iv); byte[] buffer = new byte[1024]; int nread; while ((nread = in.read(buffer)) > 0) { encryptedOutputStream.write(buffer, 0, nread); } encryptedOutputStream.flush(); return true; } catch (IOException e) { exceptionHandle(e); return false; } } public static boolean decrypt(Cipher cipher, SecretKey pbeKey, String input, String output) { LOG.info("待解密文件:" + input); try (FileInputStream in = new FileInputStream(input); FileOutputStream out = new FileOutputStream(output)) { // 先读取salt和IV byte[] salt = new byte[SALT_LENGTH]; if (in.read(salt) != SALT_LENGTH) { LOG.warning("读取salt失败"); return false; } byte[] iv = new byte[IV_LENGTH]; if (in.read(iv) != IV_LENGTH) { LOG.warning("读取IV失败"); return false; } // 初始化Cipher:传入salt、迭代次数、IV PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, ITERATION_COUNT); IvParameterSpec ivParamSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec, ivParamSpec); // 初始化CipherInputStream处理加密内容 try (CipherInputStream cipherInputStream = new CipherInputStream(in, cipher)) { byte[] buffer = new byte[1024]; int nread; while ((nread = cipherInputStream.read(buffer)) > 0) { out.write(buffer, 0, nread); } out.flush(); return true; } } catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException ex) { ex.printStackTrace(); exceptionHandle(ex); return false; } } public static void errorHandle(String message) { System.out.println(message); } public static void exceptionHandle(Exception e) { if (e instanceof BadPaddingException) { LOG.info("密码错误:" + e.getMessage()); } else if (e instanceof FileNotFoundException) { LOG.info("文件不存在:" + e.getMessage()); } else if (e instanceof InvalidKeyException) { LOG.info("密钥无效:" + e.getMessage()); } else if (e instanceof NoSuchAlgorithmException) { LOG.info("算法不存在:" + e.getMessage()); } else if (e instanceof InvalidAlgorithmParameterException) { LOG.info("参数无效:" + e.getMessage()); } else if (e instanceof IllegalArgumentException) { LOG.info("参数长度错误:" + e.getMessage()); } else if (e instanceof NoSuchPaddingException) { LOG.info("填充模式无效:" + e.getMessage()); } else { LOG.info("未知错误:" + e.getMessage()); } } }
关键修复点说明
- 加密时新增
cipher.getIV()获取自动生成的IV,并将其写入加密文件 - 解密时先读取salt和IV,再用
PBEParameterSpec(salt+迭代次数)和IvParameterSpec(IV)共同初始化Cipher - 调整了解密时流的创建顺序:先初始化Cipher,再创建
CipherInputStream,避免未初始化的Cipher被使用
内容的提问来源于stack exchange,提问作者SmokedPorcupine
相关产品推荐
相关产品推荐

