DESUtils 加解密时 Given final block not properly padded bug小记

事情的经过是这个样子的。。。。。。
先说说问题是怎么出现的。根据客户需求,需要完成一个一键登录的功能,于是我的项目中就诞生了DesUtil,但是经过上百次用户测试,发现有一个用户登录就一直报错!难道又遇到神坑啦!!发火
让我们先看看源代码,干货来了!

package com.kwp.main.util.security;

import java.io.IOException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class DesUtil {

    private final static String DES = "DES";
    public final static String KEY = "EA22DAB57022E2560A376749E3408196A9E287D800E068E5";
    /**
     * Description 根据键值进行加密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String key) throws Exception {
        byte[] bt = encrypt(data.getBytes(), key.getBytes());
        String strs = new BASE64Encoder().encode(bt);
        return strs;
    }

    /**
     * Description 根据键值进行解密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static String decrypt(String data, String key) throws IOException,
            Exception {
        if (data == null)
            return null;
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] buf = decoder.decodeBuffer(data);
        byte[] bt = decrypt(buf,key.getBytes());
        return new String(bt);
    }

    /**
     * Description 根据键值进行加密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        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(data);
    } 

    /**
     * Description 根据键值进行解密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        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(data);
    }

    public static void main(String[] args)throws Exception{
        //加密
        System.out.println(DesUtil.encrypt("liujianyong", DesUtil.KEY));

        //解密
        System.out.println(DesUtil.decrypt("/s=", DesUtil.KEY));
    }
}

就是这个人,main方法加解密是不会报错, 而嵌套到URL中就报错了,细心的我最后发现别人转码后有一个“/”, 而这个人转了之后却又两个“/”,于是我想到了URLEncoder,加上之后,确实不报错了,而是后台出来了个大大的bug, 就是这个“Given final block not properly padded”,所以不得不去网上寻找解药。

以下是网上解决方案,非本人原创,特此声明!


仔细分析一下,不难发现,该异常是在解密的时候抛出的,加密的方法没有问题。

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

public static String DataEncrypt(String str,byte[] key){

String encrypt = null;

try{

byte[] ret = encode(str.getBytes(“UTF-8”),key);

encrypt = new String(Base64.encode(ret));

}catch(Exception e){

System.out.print(e);

encrypt = str;

}

return encrypt;

}

public static String DataDecrypt(String str,byte[] key){

String decrypt = null;

try{

byte[] ret = decode(Base64.decode(str),key);

decrypt = new String(ret,”UTF-8”);

}catch(Exception e){

System.out.print(e);

decrypt = str;

}

return decrypt;

}

我们把方法的参数改成了字串,但是为什么要用UTF-8呢?不指定它的字节格式不行吗?大家知道,UTF-8是国际通用的字符编码,用它传输任何字串都不会有问题,通过它也可以很完美的解决J2EE的中文问题!所以我们最好用UTF-8编码,以减少不必要的麻烦。


结合上述方法,我的DesUtil修改版诞生了!

package com.kwp.main.util.security;

import java.io.IOException;
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.xml.internal.security.utils.Base64;

public class DesUtil {

    private final static String DES = "DES";
    public final static String KEY = "EA22DAB57022E2560A376749E3408196A9E287D800E068E5";

    /**
     * Description 根据键值进行加密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        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(data);
    }


    /**
     * Description 根据键值进行解密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        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(data);
    }


    /**
     * Description 根据键值进行加密
     * @param data 
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    public static String encrypt(String data, String key) throws Exception {

        if (data == null) return null;

        String strs = null;  
        try{  
            byte[] ret = encrypt(data.getBytes("UTF-8"), key.getBytes("UTF-8"));  
            strs = new String(Base64.encode(ret));  
        }catch(Exception e){  
            System.out.print(e);  
            strs = data;  
        }  
        System.out .println("加密后:" + strs);

        return strs;
    }

    /**
     * Description 根据键值进行解密
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static String decrypt(String data, String key) throws IOException, Exception {

        if (data == null) return null;
        String strs = null;
        try{  
            byte[] ret = decrypt(Base64.decode(data), key.getBytes("UTF-8"));  
            strs = new String(ret,"UTF-8");  
        }catch(Exception e){  
            System.out.print(e);  
            strs = data;  
        }  
        System.out.println("解密后:" + strs);
        return strs;
    }

    public static void main(String[] args)throws Exception{
        //加密
//      System.out.println(DesUtil.encrypt("liujianyong", "elink!@#$%"));
        String name = java.net.URLEncoder.encode(DesUtil.encrypt("liujianyong", "elink!@#$%"), "UTF-8");
        System.out.println(name);
        System.out.println(java.net.URLDecoder.decode(name, "UTF-8"));
        System.out.println(DesUtil.decrypt(java.net.URLDecoder.decode(name, "UTF-8"), "elink!@#$%"));

//      System.out.println(DesUtil.encrypt("0", DesUtil.KEY));
        //解密
//      System.out.println(DesUtil.decrypt("/sVcz2jGgPQ=", DesUtil.KEY));
    }
}

这样修改之后,当在使用这个钉子户登录时不报错,却说用户不存在,第二次点击却又没有问题了,然后就再没有报错。。。 大白天真时见鬼了,就算这个问题解决了吧,希望有缘人等遇到我说的那个错,并干掉他!小弟不胜感激。。。

后记,看了网上的很多例子,DES的加解密还是存在漏洞的,并且安全系数也不高,所以在这里就不推荐大家使用了,如果迫不得已的用了,还点背的碰到这样的错误,这个思路倒是可以借鉴一下! 大神勿喷。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值