對於AES算法,相信很多程序員小夥伴都聽過、用過,其原理本文就不介紹了,而是講講在實際項目中的應用。前幾天,項目需要跟乙方接口對接,乙方提供加密後的二維碼信息串,而我這邊負責對加密串進行解密。其中加解密算法用的就是AES 128位 無向量,加密模式爲ECB,填充模式爲PKCS7Padding,密鑰長度32位。
開始的開始,是先參考網上提供的CBC\PKCS7Padding加密模式進行改造,發現其實沒有自己想象的辣麼簡單,因爲會直接報如下圖足夠折騰自己一番的錯誤,網上了解了一下,發現jdk自帶的只是支PKCS5Padding,不支持PKCS7Padding。
最後的最後,google了一番:發現在獲取加密instance前,需要額外添加 BouncyCastleProvider() 這樣的provider。具體緣由就沒去深究了,畢竟先把問題解決了纔是頭等大事,其他的先晾在一邊!
接下來是源碼實現
1、AES-128位-無向量-ECB/PKCS7Padding
package com.debug.steadyjack.springbootMQ.server.util;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
/**
* AES加密算法util
* Created by steadyjack on 2018/4/21.
*/
public class AESUtil {
private static final String EncryptAlg ="AES";
private static final String Cipher_Mode="AES/ECB/PKCS7Padding";
private static final String Encode="UTF-8";
private static final int Secret_Key_Size=32;
private static final String Key_Encode="UTF-8";
/**
* AES/ECB/PKCS7Padding 加密
* @param content
* @param key 密鑰
* @return aes加密後 轉base64
* @throws Exception
*/
public static String aesPKCS7PaddingEncrypt(String content, String key) throws Exception {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(Cipher_Mode);
byte[] realKey=getSecretKey(key);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(realKey,EncryptAlg));
byte[] data=cipher.doFinal(content.getBytes(Encode));
String result=new Base64().encodeToString(data);
return result;
} catch (Exception e) {
e.printStackTrace();
throw new Exception("AES加密失敗:content=" +content +" key="+key);
}
}
/**
* AES/ECB/PKCS7Padding 解密
* @param content
* @param key 密鑰
* @return 先轉base64 再解密
* @throws Exception
*/
public static String aesPKCS7PaddingDecrypt(String content, String key) throws Exception {
try {
//Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] decodeBytes=Base64.decodeBase64(content);
Cipher cipher = Cipher.getInstance(Cipher_Mode);
byte[] realKey=getSecretKey(key);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(realKey,EncryptAlg));
byte[] realBytes=cipher.doFinal(decodeBytes);
return new String(realBytes, Encode);
} catch (Exception e) {
e.printStackTrace();
throw new Exception("AES解密失敗:Aescontent = " +e.fillInStackTrace(),e);
}
}
/**
* 對密鑰key進行處理:如密鑰長度不夠位數的則 以指定paddingChar 進行填充;
* 此處用空格字符填充,也可以 0 填充,具體可根據實際項目需求做變更
* @param key
* @return
* @throws Exception
*/
public static byte[] getSecretKey(String key) throws Exception{
final byte paddingChar=' ';
byte[] realKey = new byte[Secret_Key_Size];
byte[] byteKey = key.getBytes(Key_Encode);
for (int i =0;i<realKey.length;i++){
if (i<byteKey.length){
realKey[i] = byteKey[i];
}else {
realKey[i] = paddingChar;
}
}
return realKey;
}
}
下面進行測試:對一串序列化後的json格式字符串進行加密,密鑰設定爲 debug,測試代碼如下:
public static void main(String[] args) throws Exception{
//密鑰 加密內容(對象序列化後的內容-json格式字符串)
String key="debug";
String content="{\"domain\":{\"method\":\"getDetails\",\"url\":\"http://www.baidu.com\"},\"name\":\"steadyjack_age\",\"age\":\"23\",\"address\":\"Canada\",\"id\":\"12\",\"phone\":\"15627284601\"}";
String encryptRes=aesPKCS7PaddingEncrypt(content,key);
System.out.println(String.format("加密結果:%s ",encryptRes));
String decryptRes=aesPKCS7PaddingDecrypt(encryptRes,key);
System.out.println(String.format("解密結果:%s ",decryptRes));
}
結果如下
加密結果:mbHFRIwY+aKSFY8NMm3StyekTwmxOUu4YIYOybz6I1qeXofPnYyTEkJr2wjHBFOGdPlJpGq2BjbBN5Dakq27qj+xoyIxKzO52huWzQf8UFCaslKEZOZ3Ub3nuJAepFUzU3JvvoSp/Ei7FHKTgwgWFOs9Oq+A1deOWHyb8fqnwoQTp2zBwzaSdLU4MFf9V2MpTT6H3tP6gf97snSNfQW7VA==
解密結果:{"domain":{"method":"getDetails","url":"http://www.baidu.com"},"name":"steadyjack_age","age":"23","address":"Canada","id":"12","phone":"15627284601"}
附註:
(1)如果想改爲 PKCS5Padding 填充模式也是可以的,只需要修改上面工具類的靜態常量Cipher_Mode的取值即可,如下所示:
private static final String Cipher_Mode="AES/ECB/PKCS5Padding";
(2)上面如果想改爲 有向量模式 可以繼續參考 下面的文章 進行調整即可
2、AES-128位-有向量-CBC/PKCS5Padding
package com.debug.steadyjack.springbootMQ.server.util;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
/**
* AES加解密工具
* Created by steadyjack on 2018/2/9.
*/
public class EncryptUtil {
private static final String CipherMode="AES/CBC/PKCS5Padding";
private static final String SecretKey="debug";
private static final Integer IVSize=16;
private static final String EncryptAlg ="AES";
private static final String Encode="UTF-8";
private static final int SecretKeySize=32;
private static final String Key_Encode="UTF-8";
/**
* 創建密鑰
* @return
*/
private static SecretKeySpec createKey(){
StringBuilder sb=new StringBuilder(SecretKeySize);
sb.append(SecretKey);
if (sb.length()>SecretKeySize){
sb.setLength(SecretKeySize);
}
if (sb.length()<SecretKeySize){
while (sb.length()<SecretKeySize){
sb.append(" ");
}
}
try {
byte[] data=sb.toString().getBytes(Encode);
return new SecretKeySpec(data, EncryptAlg);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 創建16位向量: 不夠則用0填充
* @return
*/
private static IvParameterSpec createIV() {
StringBuffer sb = new StringBuffer(IVSize);
sb.append(SecretKey);
if (sb.length()>IVSize){
sb.setLength(IVSize);
}
if (sb.length()<IVSize){
while (sb.length()<IVSize){
sb.append("0");
}
}
byte[] data=null;
try {
data=sb.toString().getBytes(Encode);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new IvParameterSpec(data);
}
/**
* 加密:有向量16位,結果轉base64
* @param context
* @return
*/
public static String encrypt(String context) {
try {
byte[] content=context.getBytes(Encode);
SecretKeySpec key = createKey();
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.ENCRYPT_MODE, key, createIV());
byte[] data = cipher.doFinal(content);
String result=Base64.encodeBase64String(data);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解密
* @param context
* @return
*/
public static String decrypt(String context) {
try {
byte[] data=Base64.decodeBase64(context);
SecretKeySpec key = createKey();
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.DECRYPT_MODE, key, createIV());
byte[] content = cipher.doFinal(data);
String result=new String(content,Encode);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception{
//密鑰 加密內容(對象序列化後的內容-json格式字符串)
String content="{\"domain\":{\"method\":\"getDetails\",\"url\":\"http://www.baidu.com\"},\"name\":\"steadyjack_age\",\"age\":\"23\",\"address\":\"Canada\",\"id\":\"12\",\"phone\":\"15627284601\"}";
String encryptText=encrypt(content);
String decryptText=decrypt(encryptText);
System.out.println(String.format("明文:%s \n加密結果:%s \n解密結果:%s ",content,encryptText,decryptText));
}
}
測試結果如下:
明文:{"domain":{"method":"getDetails","url":"http://www.baidu.com"},"name":"steadyjack_age","age":"23","address":"Canada","id":"12","phone":"15627284601"}
加密結果:lh/Z4sMCweHTKzpXCrIIa7UNW+t+XBwiHz9XgDiv7ETofEJAGq7FDYaWRaeAaoAbgyuaV/vcp7/W8p07z3u7uR5Q9PMPznpgQUM/pVjoE5yO51viwEb/7QYYttRsHQAqHberj8MQEs4I7qGrdkD36bvCnXYLDpPXvkLt1LwP2VD7VXhf+eQCCsxsUilxcVv+rGa+q1QuamrfGKLzlBCkiA==
解密結果:{"domain":{"method":"getDetails","url":"http://www.baidu.com"},"name":"steadyjack_age","age":"23","address":"Canada","id":"12","phone":"15627284601"}
附註:CBC模式需要有密鑰以及初始化向量的!
彩蛋
好了,關於AES的加密模式我就介紹這幾種吧,對於當中代碼有需要討論或者有其他加解密算法需要實現的可以下面或者歡迎加羣討論:java開源技術交流羣-583522159-我是debug
另外,如有感興趣的童鞋,可以關注我的微信公衆號,裏面會不時的分享一些乾貨跟福利哦: