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

使用PBEWITHHMACSHA512ANDAES_256解密时遇块长度异常求助

解密时抛出"Input length must be multiple of 16"异常的解决方案

问题描述

加密字符串功能可正常运行,但解密时抛出异常:

Input length must be multiple of 16 when decrypting with padded cipher

原代码

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

public class Trial {

    static String key = "USSWIF3925NJ50R";
    static String userName = "sam1234t";

    public static void main(String[] arg) throws IllegalBlockSizeException, BadPaddingException {

        Trial trial = new Trial();

        Cipher cipher = trial.initialiseCipher("encrypt");

        // Finally generate the transient key in bytes.
        byte[] transientKey = cipher.doFinal(userName.getBytes(StandardCharsets.UTF_8));

        String encryptedPassword = Base64.getEncoder().encodeToString(transientKey);

        System.out.println("Encrypted Key: " + encryptedPassword);

        cipher = trial.initialiseCipher("decrypt");

        // Finally decrypt the transient key in bytes.
        byte[] temp = cipher.doFinal(encryptedPassword.getBytes(StandardCharsets.UTF_8));
        String decryptedKey = new String(Base64.getDecoder().decode(temp));

        System.out.println("Decrypted Key: " + decryptedKey);
    }


    private Cipher initialiseCipher(String mode) {

        Cipher cipher = null;

        try {

            Security.setProperty("crypto.policy", "unlimited");

            SecureRandom secureRandom = new SecureRandom();
            byte[] salt = new byte[256];
            byte[] ivBytes = new byte[16];

            secureRandom.nextBytes(salt);
            secureRandom.nextBytes(ivBytes);

            // Generate the Key Specs.
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec);
            PBEKeySpec pbeKeySpec = new PBEKeySpec((key + userName).toCharArray());

            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
            SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);

            cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256");

            if (mode.equals("encrypt")) {
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec);
            } else if (mode.equals("decrypt")) {
                cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
            }

        } catch (NoSuchAlgorithmException e) {

            e.printStackTrace();

        } catch (InvalidKeySpecException e) {

            e.printStackTrace();

        } catch (NoSuchPaddingException e) {

            e.printStackTrace();

        } catch (InvalidKeyException e) {

            e.printStackTrace();

        } catch (InvalidAlgorithmParameterException e) {

            e.printStackTrace();
        }

        return cipher;
    }
}

运行错误输出

Encrypted Key: 9DfMJ/yuEFobrdGVsVkhpQ==
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
    at com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:1005)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:848)
    at com.sun.crypto.provider.PBES2Core.engineDoFinal(PBES2Core.java:323)
    at javax.crypto.Cipher.doFinal(Cipher.java:2164)
    at com.att.logicalprovisioning.simulators.Trial.main(Trial.java:43)

问题根源及修复方案

两个核心问题

  1. 解密输入错误:直接把Base64字符串的字节数组传给cipher.doFinal,但解密需要的是Base64解码后的原始加密字节。Base64字符串长度不一定是16的倍数,导致块大小不匹配异常。
  2. Salt和IV不复用:每次调用initialiseCipher都会生成新的随机salt和IV,而PBE加密算法要求解密时必须使用和加密完全相同的salt、IV以及迭代次数,否则无法正确还原密钥和加密内容。

修复后的代码

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

public class Trial {

    static String key = "USSWIF3925NJ50R";
    static String userName = "sam1234t";
    // 保存加密时使用的salt和IV,实际场景中可与加密数据一起存储
    private static byte[] encryptionSalt;
    private static byte[] encryptionIv;

    public static void main(String[] arg) throws IllegalBlockSizeException, BadPaddingException {

        Trial trial = new Trial();

        // 加密流程
        Cipher encryptCipher = trial.initialiseCipher("encrypt");
        byte[] encryptedBytes = encryptCipher.doFinal(userName.getBytes(StandardCharsets.UTF_8));
        
        // 将salt、IV和加密数据拼接后Base64编码,方便存储/传输
        byte[] combinedData = new byte[encryptionSalt.length + encryptionIv.length + encryptedBytes.length];
        System.arraycopy(encryptionSalt, 0, combinedData, 0, encryptionSalt.length);
        System.arraycopy(encryptionIv, 0, combinedData, encryptionSalt.length, encryptionIv.length);
        System.arraycopy(encryptedBytes, 0, combinedData, encryptionSalt.length + encryptionIv.length, encryptedBytes.length);
        
        String encryptedString = Base64.getEncoder().encodeToString(combinedData);
        System.out.println("Encrypted String: " + encryptedString);

        // 解密流程
        byte[] decodedCombined = Base64.getDecoder().decode(encryptedString);
        // 拆分出salt、IV和加密数据
        byte[] decryptionSalt = new byte[256];
        byte[] decryptionIv = new byte[16];
        byte[] encryptedData = new byte[decodedCombined.length - 256 - 16];
        
        System.arraycopy(decodedCombined, 0, decryptionSalt, 0, 256);
        System.arraycopy(decodedCombined, 256, decryptionIv, 0, 16);
        System.arraycopy(decodedCombined, 256 + 16, encryptedData, 0, encryptedData.length);
        
        Cipher decryptCipher = trial.initialiseCipher("decrypt", decryptionSalt, decryptionIv);
        byte[] decryptedBytes = decryptCipher.doFinal(encryptedData);
        String decryptedKey = new String(decryptedBytes, StandardCharsets.UTF_8);

        System.out.println("Decrypted Key: " + decryptedKey);
    }

    // 重载方法:加密时自动生成salt和IV
    private Cipher initialiseCipher(String mode) {
        return initialiseCipher(mode, null, null);
    }

    // 重载方法:解密时传入加密时使用的salt和IV
    private Cipher initialiseCipher(String mode, byte[] customSalt, byte[] customIv) {

        Cipher cipher = null;

        try {

            Security.setProperty("crypto.policy", "unlimited");

            SecureRandom secureRandom = new SecureRandom();
            byte[] salt;
            byte[] ivBytes;

            // 加密模式生成新的salt和IV并保存;解密模式使用传入的参数
            if (mode.equals("encrypt")) {
                salt = new byte[256];
                ivBytes = new byte[16];
                secureRandom.nextBytes(salt);
                secureRandom.nextBytes(ivBytes);
                encryptionSalt = salt;
                encryptionIv = ivBytes;
            } else {
                salt = customSalt;
                ivBytes = customIv;
            }

            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, 65536, ivParameterSpec);
            PBEKeySpec pbeKeySpec = new PBEKeySpec((key + userName).toCharArray());

            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEWITHHMACSHA512ANDAES_256");
            SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);

            cipher = Cipher.getInstance("PBEWITHHMACSHA512ANDAES_256");

            if (mode.equals("encrypt")) {
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec);
            } else if (mode.equals("decrypt")) {
                cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
            }

        } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }

        return cipher;
    }
}

关键修改说明

  1. 解密输入修正:解密时先对Base64字符串解码,拆分出salt、IV和原始加密数据,再将加密数据传给cipher.doFinal。
  2. Salt和IV复用:加密时生成并保存salt和IV,解密时传入相同值;实际项目中可将三者拼接后一起存储/传输,避免参数丢失。
  3. 方法重载:新增带salt和IV参数的初始化方法,适配解密场景的参数需求。

内容的提问来源于stack exchange,提问作者hell_storm2004

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:50:24