事情的經過是這個樣子的。。。。。。
先說說問題是怎么出現的。根據客戶需求,需要完成一個一鍵登錄的功能,于是我的項目中就誕生了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的加解密還是存在漏洞的,并且安全系數也不高,所以在這里就不推薦大家使用了,如果迫不得已的用了,還點背的碰到這樣的錯誤,這個思路倒是可以借鑒一下! 大神勿噴。。。