/**
* AES算法加密。JRE默认只能用16个字节(128)位密钥
*/
public class AESUtils {
//使用指定转换的 Cipher 对象
public static final String CIPHER_ALGORITHM_AES = "AES";
public static final String CIPHER_ALGORITHM_ECB = "AES/ECB/PKCS5Padding";
public static final String CIPHER_ALGORITHM_CBC = "AES/CBC/PKCS5Padding";
//【AES/CBC/NoPadding】模式下,待加密内容的长度必须是16的倍数,否则: javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
public static final String CIPHER_ALGORITHM_CBC_NoPadding = "AES/CBC/NoPadding";
private static final String ALGORITHM_MD5 = "md5";
private static final String CHARSET = "UTF-8";
private static final String ALGORITHM_AES = "AES";
public static void main(String[] args) throws Exception {
byte[] key = getAESKeyBytes("1");//要求密钥必须是16位的
test("0123456789123456".getBytes(CHARSET), key);
test("1".getBytes(CHARSET), key);
}
private static void test(byte[] source, byte[] key) throws Exception {
System.out.println("待加密内容的长度为【" + source.length + "】密钥的长度为【" + key.length + "】");
test2(source, key, CIPHER_ALGORITHM_AES);
test2(source, key, CIPHER_ALGORITHM_ECB);
test2(source, key, CIPHER_ALGORITHM_CBC);
test2(source, key, CIPHER_ALGORITHM_CBC_NoPadding);
System.out.println("================================================");
}
private static void test2(byte[] source, byte[] key, String mode) throws Exception {
//生成的密文
byte[] cryptograph = encryptOrDecrypt(source, key, mode, Cipher.ENCRYPT_MODE);
//通过Base64编码为ASCII字符后传输
String cryptographStr = Base64.getEncoder().encodeToString(cryptograph);
//收到后先用Base64解码
byte[] targetBase64 = Base64.getDecoder().decode(cryptographStr.getBytes(CHARSET));
// 解密密文
byte[] target = encryptOrDecrypt(targetBase64, key, mode, Cipher.DECRYPT_MODE);
System.out.println("加密前【" + new String(source, CHARSET) + "】\n加密后【" + cryptographStr + "】\n解密后【" + new String(target, CHARSET) + "】");
//如果原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,其它情况下加密数据长度等于16*(n+1)
//在不足16的整数倍的情况下,假如原始数据长度等于16*n+m(m小于16),除了NoPadding填充之外的任何方式,加密数据长度都等于16*(n+1)
System.out.println("加密前字节数【" + source.length + "】加密后字节数【" + cryptograph.length + "】解密后字节数【" + target.length + "】\n");
}
/**
* 加密或解密。加密和解密用的同一个算法和密钥
* @param source 要加密或解密的数据
* @param key 密钥
* @param transformation
* @param mode 加密或解密模式。值请选择Cipher.DECRYPT_MODE或Cipher.ENCRYPT_MODE
* @return 加密或解密后的数据
*/
public static byte[] encryptOrDecrypt(byte[] source, byte[] key, String transformation, int mode) throws Exception {
Cipher cipher = Cipher.getInstance(transformation);
Key secretKey = new SecretKeySpec(key, ALGORITHM_AES); //密钥
if (transformation.equals(CIPHER_ALGORITHM_CBC) || transformation.equals(CIPHER_ALGORITHM_CBC_NoPadding)) {
cipher.init(mode, secretKey, new IvParameterSpec(getIV()));//指定一个初始化向量 (Initialization vector,IV), IV 必须是16位
return cipher.doFinal(source);
} else {
cipher.init(mode, secretKey);
return cipher.doFinal(source);
}
}
/**
* 根据字符串生成AES的密钥字节数组<br>
*/
public static byte[] getAESKeyBytes(String sKey) throws Exception {
//获得指定摘要算法的 MessageDigest 对象
MessageDigest md = MessageDigest.getInstance(ALGORITHM_MD5);
//使用指定的字节更新摘要(继续多部分加密或解密操作,以处理其他数据部分)
md.update(sKey.getBytes(CHARSET));
//获得密文。注意:长度为16而不是32。一个字节(byte)占8位(bit)
return md.digest();
}
/**
* 指定一个初始化向量 (Initialization vector,IV),IV 必须是16位
*/
private static final byte[] getIV() throws Exception {
return "1234567812345678".getBytes(CHARSET);
}
}待加密内容的长度为【16】密钥的长度为【16】
加密前【0123456789123456】
加密后【sYT9Qk02gZc20Xfxgr5I6QPIY0E8Zrgj6d20wPlxxCg=】
解密后【0123456789123456】
加密前字节数【16】加密后字节数【32】解密后字节数【16】
加密前【0123456789123456】
加密后【sYT9Qk02gZc20Xfxgr5I6QPIY0E8Zrgj6d20wPlxxCg=】
解密后【0123456789123456】
加密前字节数【16】加密后字节数【32】解密后字节数【16】
加密前【0123456789123456】
加密后【W4Lsqr4gXF2B/JuT5A46N1dslm+BNO+l40T36CUcPNg=】
解密后【0123456789123456】
加密前字节数【16】加密后字节数【32】解密后字节数【16】
加密前【0123456789123456】
加密后【W4Lsqr4gXF2B/JuT5A46Nw==】
解密后【0123456789123456】
加密前字节数【16】加密后字节数【16】解密后字节数【16】
================================================
待加密内容的长度为【1】密钥的长度为【16】
加密前【1】
加密后【EfgHFA7AwlCEtnAW6VJG5A==】
解密后【1】
加密前字节数【1】加密后字节数【16】解密后字节数【1】
加密前【1】
加密后【EfgHFA7AwlCEtnAW6VJG5A==】
解密后【1】
加密前字节数【1】加密后字节数【16】解密后字节数【1】
加密前【1】
加密后【I1neHBjrE8NoYocaxiCuDg==】
解密后【1】
加密前字节数【1】加密后字节数【16】解密后字节数【1】
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes