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

大文件加密解密异常:解密时抛出IllegalBlockSizeException

解决AES解密时IllegalBlockSizeException: last block incomplete异常

问题根源

你的代码中TRANSFORMATION使用纯"AES",会默认启用AES/ECB/NoPadding模式,两个核心问题导致了异常:

  1. ECB模式安全性极低:相同明文块会生成完全相同的密文块,极易被破解;
  2. NoPadding对块大小要求严格:AES块固定为16字节,NoPadding模式下明文长度必须是16的整数倍。小文件运行正常大概率是刚好满足字节数条件,大文件不满足时,加密后的密文长度不是16的整数倍,解密时就会抛出该异常。

修复方案

推荐使用带填充的CBC模式(兼容性好)或更安全的GCM认证加密模式,以下是具体实现:

方案1:AES/CBC/PKCS5Padding(兼容常见场景)

CBC模式需要随机初始化向量(IV),加密时将IV写入密文头部,解密时先读取IV再解密。

  1. 修改转换模式常量:
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
  1. 更新核心doCrypto方法:
private static void doCrypto(int cipherMode, String key, File inputFile, File outputFile) {
    try {
        Key secretKey = generateKey(key);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        
        byte[] iv;
        FileInputStream inputStream;

        if (cipherMode == Cipher.ENCRYPT_MODE) {
            // 生成16字节随机IV
            iv = new byte[16];
            new SecureRandom().nextBytes(iv);
            cipher.init(cipherMode, secretKey, new IvParameterSpec(iv));
            inputStream = new FileInputStream(inputFile);
        } else {
            // 解密时先读取文件头部的IV
            inputStream = new FileInputStream(inputFile);
            iv = new byte[16];
            inputStream.read(iv);
            cipher.init(cipherMode, secretKey, new IvParameterSpec(iv));
            
            // 跳过已读取的IV,创建新的输入流处理密文内容
            inputStream = new FileInputStream(inputFile) {
                {
                    skip(16);
                }
            };
        }

        FileOutputStream outputStream = new FileOutputStream(outputFile);
        
        // 加密时先写入IV到文件头部
        if (cipherMode == Cipher.ENCRYPT_MODE) {
            outputStream.write(iv);
        }

        // 用8KB缓冲区提升大文件处理效率
        byte[] buff = new byte[8192];
        int readBytes;
        while ((readBytes = inputStream.read(buff)) != -1) {
            outputStream.write(cipher.update(buff, 0, readBytes));
        }
        outputStream.write(cipher.doFinal());

        inputStream.close();
        outputStream.close();

    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

方案2:AES/GCM/NoPadding(更安全,支持数据完整性校验)

GCM是AEAD模式,既加密又验证数据完整性,无需填充,适合任意长度数据。需要12字节随机IV,加密时将IV和认证标签写入文件,解密时读取后验证。

  1. 修改转换模式常量:
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
  1. 更新核心doCrypto方法:
private static void doCrypto(int cipherMode, String key, File inputFile, File outputFile) {
    try {
        Key secretKey = generateKey(key);
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        
        byte[] iv;
        FileInputStream inputStream;

        if (cipherMode == Cipher.ENCRYPT_MODE) {
            // GCM推荐使用12字节IV
            iv = new byte[12];
            new SecureRandom().nextBytes(iv);
            GCMParameterSpec spec = new GCMParameterSpec(128, iv);
            cipher.init(cipherMode, secretKey, spec);
            inputStream = new FileInputStream(inputFile);
        } else {
            // 解密时先读取IV和认证标签
            inputStream = new FileInputStream(inputFile);
            iv = new byte[12];
            inputStream.read(iv);
            byte[] tag = new byte[16];
            inputStream.read(tag);
            
            GCMParameterSpec spec = new GCMParameterSpec(128, iv);
            cipher.init(cipherMode, secretKey, spec);
            // 设置需要验证的标签
            cipher.updateAAD(tag);
            
            // 跳过IV和标签,处理密文内容
            inputStream = new FileInputStream(inputFile) {
                {
                    skip(12 + 16);
                }
            };
        }

        FileOutputStream outputStream = new FileOutputStream(outputFile);
        
        // 加密时先写入IV
        if (cipherMode == Cipher.ENCRYPT_MODE) {
            outputStream.write(iv);
        }

        byte[] buff = new byte[8192];
        int readBytes;
        while ((readBytes = inputStream.read(buff)) != -1) {
            outputStream.write(cipher.update(buff, 0, readBytes));
        }
        byte[] finalBytes = cipher.doFinal();
        
        if (cipherMode == Cipher.ENCRYPT_MODE) {
            // 写入加密内容和认证标签
            outputStream.write(finalBytes);
            outputStream.write(cipher.getGCMTag());
        } else {
            outputStream.write(finalBytes);
        }

        inputStream.close();
        outputStream.close();

    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

额外优化建议

  • 禁止使用ECB模式,安全性极低;
  • 缓冲区大小建议用8192字节或更大,提升大文件IO效率;
  • generateKey中的SALT应该用固定的字节数组(如private static final byte[] SALT = "your-fixed-salt-here".getBytes(StandardCharsets.UTF_8);),避免字符编码差异导致密钥不一致;
  • 不要仅打印异常堆栈,建议根据业务场景抛出自定义异常或进行针对性错误处理。

内容的提问来源于stack exchange,提问作者Михаил Карлов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 01:47:08