js和java中的AES加密和解密
阿新 • • 發佈:2019-01-05
每次都要在這個問題上耗費一天的時間,所以這次留下記錄免得以後麻煩。
JS端使用CryptoJS,服務端bouncy castle提供的AES演算法。
AES演算法採用“AES/CBC/PKCS7Padding”,這個在JS和JAVA中都支援。Java預設的加密演算法中,不支援PKCS7Padding,只支援PKCS5Padding,bouncy castle支援PKCS7Padding;CryptoJS中沒有Pkcs5,只有Pkcs7。所以最後才選擇js部分用CryptoJS和java部分用bouncy castle的實現。
Java部分的“AES/CBC/PKCS7Padding”描述的內容是這樣的,AES是加密解密演算法;CBC是加密過程中如何分塊,還有加密各個塊的時候如何更換金鑰;PKCS7Padding是加密資料不夠一塊的時候如何填補剩餘空間的。AES+CBC+PKCS7Padding這樣的組合是CryptoJS的預設設定。
//JsonFormatter是用來把加密結果格式化的工具。如果不設定,加密結果toString()之後是base64編碼的密文。
var JsonFormatter = {
stringify: function (cipherParams) {
// create json object with ciphertext
var jsonObj = {
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Hex)
};
// optionally add iv and salt
if (cipherParams.iv) {
jsonObj.iv = cipherParams.iv.toString();
}
if (cipherParams.salt) {
jsonObj.s = cipherParams.salt.toString();
}
// stringify json object
return JSON.stringify(jsonObj);
},
parse: function (jsonStr) {
// parse json string
var jsonObj = JSON.parse(jsonStr);
// extract ciphertext from json object, and create cipher params object
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Hex.parse(jsonObj.ct)
});
// optionally extract iv and salt
if (jsonObj.iv) {
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
}
if (jsonObj.s) {
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
}
return cipherParams;
}
};
var pwd = CryptoJS.enc.Hex.parse("00000000000000000000000000000000");//密碼必須用Hex或其他方式處理為byte陣列,如果直接使用字串,CryptoJS會用加鹽的方法重新生成密碼,而且加的鹽是隨機數,這樣在java端就沒法解祕了。
var iv = CryptoJS.enc.Hex.parse('11111111111111111111111111111111');//iv在java中必須為16byte長,所以js中也必須設定為相同的長度,否則加密後的結果在java中無法解密。
var setting={iv:iv,
//mode:CryptoJS.mode.CBC, //預設值,可以不設定
//padding:CryptoJS.pad.Pkcs7,//同上
format: JsonFormatter};
var mi=CryptoJS.AES.encrypt(args.data,pwd, setting);
mi=JSON.parse(mi.toString());//mi本身是個物件,而且內部屬性迴圈引用,所以不用直接用JSON處理,需要先toString()。因為我們設定過format,所以得到一個son字串,這樣可以獲得密文和iv。
args.data=mi.ct;//ct是密文,iv是引數向量,s是鹽(密碼為字串的時候出現,否則不出現)
java的AES工具
Security.addProvider(new BouncyCastleProvider());//新增BouncyCastle的實現, 放static塊中
//下面是一些常量
/**
* IV大小.
*/
private static final int IV_SIZE = 16;
/**
* BC包中AES演算法名.
*/
public static final String ALGORITHM_LONG_NAME = "AES/CBC/PKCS7Padding";
/**
* BC包中AES演算法名.
*/
public static final String ALGORITHM_SHORT_NAME = "AES";
/**
* BC Provider名稱.
*/
public static final String PROVIDER_NAME = "BC";
//獲得加密器的函式
private static Cipher generateCipher(final int mode, final byte[] key,
final byte[] ivp) throws NoSuchAlgorithmException,
NoSuchProviderException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException {
Cipher res = null;
final SecretKey secretkey = new SecretKeySpec(key, ALGORITHM_SHORT_NAME);
final IvParameterSpec ivparameter = new IvParameterSpec(ivp);
res = Cipher.getInstance(ALGORITHM_LONG_NAME, PROVIDER_NAME);
res.init(mode, secretkey, ivparameter);
return res;
}
//java安全加密的部分對隨機數又要求,普通的隨機數是不行的,需要特殊處理,應該是長度、演算法上有區別,而且好像儲存也不一樣。使用的方法如下:
/**
* 獲得金鑰.
* @return 金鑰.
*/
public static byte[] generateKey() {
byte[] res = null;
KeyGenerator keyGen = null;
SecretKey key = null;
try {
keyGen = KeyGenerator.getInstance(ALGORITHM_SHORT_NAME, PROVIDER_NAME);
keyGen.init(new SecureRandom());
key = keyGen.generateKey();
res = key.getEncoded();
} catch (NoSuchAlgorithmException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchProviderException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
}
return res;
}
//Java的cipher可以完成加密和解密兩種功能,處理過程如下
/**
* 處理加密解密過程.
* @param input
* 輸入.
* @param cipher
* cipher.
* @return 結果.
*/
private static byte[] process(final byte[] input, final Cipher cipher) {
byte[] res = null;
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
CipherOutputStream cOut = new CipherOutputStream(bOut, cipher);
try {
cOut.write(input);
cOut.flush();
cOut.close();
res = bOut.toByteArray();
} catch (IOException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
}
return res;
}
//加密和解密介面
/**
* 加密.
* @param data
* 加密的資料.
* @param key
* 金鑰.
* @param iv
* CBC演算法所需初始矩陣.
* @return 加密結果.
*/
public static byte[] encrypt(
final byte[] data, final byte[] key, final byte[] iv
) {
byte[] res = null;
try {
res = process(data, generateCipher(Cipher.ENCRYPT_MODE, key, iv));
} catch (InvalidKeyException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchProviderException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchPaddingException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (InvalidAlgorithmParameterException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
}
return res;
}
/**
* 解密.
* @param data
* 解密的資料.
* @param key
* 金鑰.
* @param iv
* CBC演算法所需初始矩陣.
* @return 解密結果.
*/
public static byte[] decrypt(
final byte[] data, final byte[] key, final byte[] iv
) {
byte[] res = null;
try {
res = process(data, generateCipher(Cipher.DECRYPT_MODE, key, iv));
} catch (InvalidKeyException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchProviderException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (NoSuchPaddingException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
} catch (InvalidAlgorithmParameterException e) {
staticLogger(AesUtil.class).error(e.getMessage(), e);
}
return res;
}
在java中採用相同的密碼和iv就可以解密js的密文了。
解密程式碼如下:
byte[] key = Hex.decodeHex("000000000...000000000000000".toCharArray());//注意密碼長度
byte[] iv = Hex.decodeHex("11111111111111...111".toCharArray());//注意iv長度
byte[] ct = Hex.decodeHex(new String(ct).toCharArray());
byte[] res = AesUtil.decrypt(ct, key, iv);
java端加密,用於返回
byte[] key = Hex.decodeHex("111...111".toCharArray());
byte[] iv = Hex.decodeHex("000...000".toCharArray());
byte[] ct = AesUtil.encrypt(ct, key, iv);
byte[] output = Hex.encodeHexString(ct).getBytes();
js端解密
//data是寫回的資料
var msg=CryptoJS.enc.Hex.parse(data);
msg=CryptoJS.enc.Base64.stringify(msg);//CryptJS的解密方法輸入密文資料必須為Base64編碼
var decrypted = CryptoJS.AES.decrypt(msg, pwd, { iv:iv}); //密碼和iv同加密過程
var txt = (CryptoJS.enc.Utf8.stringify(decrypted).toString());//獲得明文
java加密的過程沒有直接貼工具上來,因為拆成好幾個部分寫了,涉及其它東西太多。這樣應該能復原了。