java DES 加密解密算法及报错 Given final block not properly padded

本文探讨了解决Java中使用DES加密算法时遇到的BadPaddingException异常问题,并提供了一种通过Base64编码确保数据完整性的解决方案。

解密的时候报错: 

javax.crypto.BadPaddingException:   Given   final   block   not   properly   padded 

该异常是在解密的时候抛出的,加密的方法没有问题。  

但是两个方法的唯一差别是Cipher对象的模式不一样,这就排除了程序写错的可能性。再看一下异常的揭示信息,大概的意思是:提供的字块不符合填补的。原来在用DES加密的时候,最后一位长度不足64的,它会自动填补到64,那么在我们进行字节数组到字串的转化过程中,可以把它填补的不可见字符改变了,所以引发系统抛出异常。大家还记得邮件传输通常会把一些信息编码保存,就是Base64,那样保证了信息的完整性,所以我们就是利用一下下了。为了方便使用,再写一个新的方法封装一下原来的方法:  1.  DataDecrypt   2.  DataEncrypt

加密:

import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;


import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
public class DescryptCoder {
      
  private final static String DES = "DES";
      
  /**
   *
   * @param src 数据源
   * @param key 密钥,长度必须是8的倍数
   * @return
   * @throws Exception
   */
  public static byte[] decrypt(byte[] src, byte[] key) throws Exception {
    // DES算法要求有一个可信任的随机数源
    SecureRandom sr = new SecureRandom();
    // 从原始密匙数据创建一个DESKeySpec对象
    DESKeySpec dks = new DESKeySpec(key);
    // 创建一个密匙工厂,然后用它把DESKeySpec对象转换成一个SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
    SecretKey securekey = keyFactory.generateSecret(dks);
    // Cipher对象实际完成解密操作
    Cipher cipher = Cipher.getInstance(DES);
    // 用密匙初始化Cipher对象
    cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
        
    // 正式执行解密操作
    return cipher.doFinal(src);
  }
      
  public final static String decrypt(String data, String key) {
    try {
    //这里就没走
      return new String(decrypt(String2byte(data.getBytes()), key.getBytes()));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }
      
  public static byte[] String2byte(byte[] b) {
    if ((b.length % 2) != 0)
      throw new IllegalArgumentException("长度不是偶数");
    byte[] b2 = new byte[b.length / 2];
    for (int n = 0; n < b.length; n += 2) {
      String item = new String(b, n, 2);
      b2[n / 2] = (byte) Integer.parseInt(item, 16);
    }
    return b2;
  }
  public static String DataDecrypt(String str,byte[] key){  
 String decrypt = null;  
 try{  
 byte[] ret = decrypt(Base64.decode(str),key);  
 decrypt = new String(ret,"UTF-8");  
 }catch(Exception e){  
 System.out.print(e);  
 decrypt = str;  
 }  
 return decrypt;  


 } 
  public static void main(String[] args){
    String desencryptString = decrypt("B00542E93695F4CFCE34FC4393C2F4BF","test中英文杂七烂八混搭@123654{");
    System.out.println(desencryptString);
  }
  //输出:is张三丰
}




解密:


import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;


import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
public class EncryptCoder {
            
  private final static String DES = "DES";
            
  public static byte[] encrypt(byte[] src, byte[] key) throws Exception {
    // DES算法要求有一个可信任的随机数源
    SecureRandom sr = new SecureRandom();
    // 从原始密匙数据创建DESKeySpec对象
    DESKeySpec dks = new DESKeySpec(key);
    // 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES);
    SecretKey securekey = keyFactory.generateSecret(dks);
    // Cipher对象实际完成加密操作
    Cipher cipher = Cipher.getInstance(DES);
    // 用密匙初始化Cipher对象
    cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
    // 正式执行加密操作
    return cipher.doFinal(src);
  }
  /**
  *
  * @param password 密码
  * @param key 加密字符串
  * @return
  */
 public final static String encrypt(String password, String key) {
   try {
     return byte2String(encrypt(password.getBytes(), key.getBytes()));
   } catch (Exception e) {
   }
   return null;
 }
           
 public static String byte2String(byte[] b) {
   String hs = "";
   String stmp = "";
   for (int n = 0; n < b.length; n++) {
     stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
     if (stmp.length() == 1)
       hs = hs + "0" + stmp;
     else
       hs = hs + stmp;
   }
   return hs.toUpperCase();
 }   
 public static String DataEncrypt(String str,byte[] key){  


 String encrypt = null;  
 try{  
 byte[] ret = encrypt(str.getBytes("UTF-8"),key);  
 encrypt = new String(Base64.encode(ret));  
 }catch(Exception e){  
 System.out.print(e);  
 encrypt = str;  
 }  
 return encrypt;  
 }  
 public static void main(String[] args){
   String encryptString = EncryptCoder.encrypt("is张三丰","test中英文杂七烂八混搭@123654{");
   System.out.println("加密后:"+encryptString);
 } 
 //输出:B00542E93695F4CFCE34FC4393C2F4BF   
}


<think>我们正在解决Java解密时出现的“Given final block not properly padded”错误。根据引用内容,该错误通常与加解密模式、填充方式、密钥错误或数据损坏有关。以下将结合引用内容逐步分析并提供解决方案。 ### 错误原因分析 1. **加解密模式或填充方案不一致**(引用[3]): - 不同系统/环境可能使用不同的默认模式(如Windows默认可能是AES/ECB/PKCS5Padding,而Linux可能是其他模式) - 如果加密时使用ECB模式,而解密时使用其他模式(如CBC),则会导致填充错误 2. **数据在传输过程中被修改**(引用[2]): - 当加密数据通过URL传输时,可能发生字符转义(如`/`被编码为`%2F`,或Base64中的`/`被误处理) - 多次URL编码可能导致数据长度变化,破坏填充结构 3. **密钥错误或数据损坏**(引用[1][5]): - 使用错误的密钥解密 - 密文被截断或不完整 ### 解决方案 #### 方法1:明确指定加解密算法模式与填充方式(推荐) ```java // 加密Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 解密时(必须与加密时一致) Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); ``` > **关键点**:避免依赖环境默认值,显式声明`算法/模式/填充`三元组[^3] #### 方法2:处理URL传输中的编码问题 若密文通过URL传输: ```java // 发送端:对Base64密文进行URL编码 String safeData = URLEncoder.encode(base64EncryptedData, "UTF-8"); // 接收端:先URL解码再解密 String decodedData = URLDecoder.decode(receivedData, "UTF-8"); byte[] encryptedBytes = Base64.getDecoder().decode(decodedData); ``` > **注意**:避免双重编码(引用[2]中因多出一个`/`导致错误)[^2] #### 方法3:验证数据完整性 ```java // 检查数据长度(AES要求为16字节倍数) if (encryptedData.length % 16 != 0) { throw new IllegalArgumentException("Invalid data length: " + encryptedData.length); } ``` #### 方法4:跨平台密钥一致性处理 ```java // 确保密钥在不同平台编码一致(如UTF-8) SecretKeySpec key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES"); ``` ### 完整修复示例 ```java import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; public class AesDecryptFix { public static String decrypt(String encryptedData, String key) throws Exception { // Step1: URL解码(如果通过URL传输) String decodedData = URLDecoder.decode(encryptedData, "UTF-8"); // Step2: Base64解码 byte[] encryptedBytes = Base64.getDecoder().decode(decodedData); // Step3: 验证数据长度 if (encryptedBytes.length % 16 != 0) { throw new IllegalArgumentException("Invalid data length: " + encryptedBytes.length); } // Step4: 创建密钥(指定字符集) SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); // Step5: 显式指定算法/模式/填充 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); // Step6: 解密 byte[] decryptedBytes = cipher.doFinal(encryptedBytes); return new String(decryptedBytes, StandardCharsets.UTF_8); } public static void main(String[] args) throws Exception { String key = "d7b85c6e9c3d6b0a"; String encryptedData = "URL编码后的Base64字符串"; System.out.println("解密结果: " + decrypt(encryptedData, key)); } } ``` ### 错误排查流程图 ```mermaid graph TD A[出现BadPaddingException] --> B{检查模式/填充一致性} B -->|不一致| C[显式指定AES/ECB/PKCS5Padding] B -->|一致| D{数据是否通过URL传输} D -->|是| E[添加URL编解码步骤] D -->|否| F{检查密钥一致性} F -->|密钥错误| G[统一密钥字符集为UTF-8] F -->|正确| H{验证数据长度} H -->|长度错误| I[检查Base64解码过程] H -->|长度正确| J[检查原始数据完整性] ``` ### 关键预防措施 1. **始终显式声明加密参数**: ```java // 不要使用 Cipher.getInstance("AES"); // 而要用 Cipher.getInstance("AES/ECB/PKCS5Padding"); ``` 2. **跨平台数据传输规范**: - 传输前:Base64编码 → URL编码 - 接收后:URL解码 → Base64解码 → 解密 3. **使用日志记录关键步骤**: ```java logger.debug("原始数据长度: {}", encryptedData.length()); logger.debug("URL解码后: {}", decodedData); logger.debug("Base64解码长度: {}", encryptedBytes.length); ``` > **提示**:在Linux环境中可设置`-Dfile.encoding=UTF-8`启动参数确保编码一致[^1]。 §§相关问题§§ 1. Java中AES加密有哪些安全的模式推荐? 2. 如何避免URL传输导致的Base64数据损坏? 3. 不同操作系统下Java加密行为差异如何解决?</think>### 解决Java解密错误 "Given final block not properly padded" 的方法 该错误通常发生在AES解密过程中,主要原因是**加密数据、密钥或解密参数不匹配**。以下是系统性的解决方案,结合引用内容分析: #### 1. 明确指定加解密算法模式与填充方式(最常见原因) ```java // 错误写法(依赖系统默认值) Cipher cipher = Cipher.getInstance("AES"); // 正确写法(显式指定模式与填充) Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // [^3] ``` > **原因**:不同系统默认值不同(如Windows和Linux差异[^1]),必须显式声明完整的`算法/模式/填充`三元组 #### 2. 处理URL传输中的特殊字符问题 ```java // URL传输前编码 String safeData = URLEncoder.encode(encryptedData, "UTF-8"); // 接收后解码再解密 String originalData = URLDecoder.decode(receivedData, "UTF-8"); decrypt(originalData); // [^2] ``` > **原因**:URL中的`/`等特殊字符会被转义,破坏Base64结构[^2] #### 3. 验证数据完整性与长度 ```java // 检查数据长度是否为16字节倍数(AES要求) if (encryptedData.length() % 16 != 0) { throw new IllegalArgumentException("Invalid data length: " + encryptedData.length()); } // [^4] ``` #### 4. 统一跨平台密钥处理 ```java // 使用UTF-8确保跨平台一致性 SecretKeySpec key = new SecretKeySpec( keyString.getBytes(StandardCharsets.UTF_8), "AES" ); // [^1] ``` #### 5. 完整解决方案代码 ```java import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Base64; public class AesDecryptor { public static String decrypt(String encryptedData, String key) throws Exception { // 1. 显式指定算法参数 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 2. 统一密钥编码 SecretKeySpec secretKey = new SecretKeySpec( key.getBytes(StandardCharsets.UTF_8), "AES" ); // 3. 初始化解密cipher.init(Cipher.DECRYPT_MODE, secretKey); // 4. Base64解码 byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData); // 5. 执行解密 return new String(cipher.doFinal(encryptedBytes), StandardCharsets.UTF_8); } // 处理URL传输的入口 public static String decryptFromURL(String urlEncodedData, String key) throws Exception { String decoded = URLDecoder.decode(urlEncodedData, StandardCharsets.UTF_8.name()); return decrypt(decoded, key); } } ``` ### 错误排查流程图 ```mermaid graph TD A[出现BadPaddingException] --> B{检查算法/模式/填充} B -->|未显式指定| C[添加完整参数 AES/ECB/PKCS5Padding] B -->|已指定| D{数据是否通过URL传输} D -->|是| E[添加URL编解码步骤] D -->|否| F{检查密钥一致性} F -->|跨平台问题| G[统一使用UTF-8编码密钥] F -->|正确| H{验证数据长度} H -->|长度错误| I[检查Base64解码] H -->|长度正确| J[检查原始数据完整性] ``` ### 预防措施 1. **始终显式声明加密参数** ```java // 推荐写法 Cipher.getInstance("AES/GCM/NoPadding"); // 避免写法 Cipher.getInstance("AES"); ``` 2. **URL传输安全协议** ```java // 发送端 String safe = URLEncoder.encode(base64Data, "UTF-8"); // 接收端 String original = URLDecoder.decode(received, "UTF-8"); ``` 3. **使用更安全的加密模式** ```java // 优先选择认证加密模式 Cipher.getInstance("AES/GCM/NoPadding"); // 比ECB更安全 ``` > **关键提示**:在Linux环境中,可通过`-Dfile.encoding=UTF-8`启动参数强制统一编码[^1]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值