js和java中的AES加密和解密

时间:2022-07-08 18:31:00

每次都要在这个问题上耗费一天的时间,所以这次留下记录免得以后麻烦。

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加密的过程没有直接贴工具上来,因为拆成好几个部分写了,涉及其它东西太多。这样应该能复原了。