You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

原因分析

  1. 未保存加密时自动生成的IV:PBEWithHmacSHA256AndAES_256底层采用AES-CBC模式,加密时Cipher会自动生成随机IV,但代码仅保存了salt,未将IV写入加密文件。解密时算法需要该IV才能正确初始化,因此抛出异常。
  2. 解密时流初始化顺序错误:先创建了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 05:37:09