AES对称加密算法加解密实践指南
1、什么是对称加密?
使用相同的密钥(Key)进行数据的加密和解密,安全性依赖于密钥的保密性。
常见对称加密算法:AES(高级加密标准)、DES(数据加密标准)、3DES(三重DES)、RC4等。
2、JAVA加解密常用类
2.1 SecretKeySpec类
SecretKeySpec 类提供了一种简便的方法,可以直接通过已知密钥(Key)创建 SecretKey,而不需要通过KeyGenerator去创建随机的密钥 和 创建SecretKey 。
2.2 AlgorithmParameters类
AlgorithmParameters 类提供了获取、设置和管理特定算法相关参数的方式。比如,CBC工作模式下的AES加密算法,需要设置一个初始化向量IV,就可以用这个类设置初始化向量。
2.3 Cipher类
Cipher 类提供加密和解密的功能,支持多种加密模式(AES、SM4等等)和 填充方式(PKCS5、PKCS7等等)。
3、实际使用
3.1 导入maven依赖
为什么要导入这个依赖,后面会介绍
不想导入这个依赖也可以,加密类有一个相同名的成员变量,用这行代码替代它,但是不推荐。因为PKCS#5适用于64位块大小的加密算法(3DES、DES、RC4),而PKCS#7更通用,适用于任何块大小。
private static final String DEFAULT_ALG_STRING = "AES/CBC/PKCS5Padding";
<!--添加Bouncy Castle 库的依赖-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
3.2 定义加解密工具类
使用CBC工作模式,需要一个iv作为初始化向量,用于增强使用AES加密算法的安全性。
同一条数据,密钥相同,iv不同的话,加密数据也会不一样,因此,加解密时必须使用相同的key和iv,否则会解密失败。
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;
import java.util.Base64;
/**
* 使用AES对称加密算法的CBC模式进行加解密
*/
public class AESCBCUtil {
//使用AES算法的 CBC模式 和 PKCS7填充
private static final String DEFAULT_ALG_STRING = "AES/CBC/PKCS7Padding";
static {
//向Java的Security系统添加一个新的安全提供者(Provider),即 BouncyCastle提供者
//BouncyCastle是一个广泛使用的开源加密库,它提供了大量的加密算法实现,包括一些不在Java标准版中的算法(比如,国密算法SM4等等)
Security.addProvider(new BouncyCastleProvider());
}
/**
* 初始化Cipher
*
* @param encrypt 判断是加密还是解密操作,true表示进行加密
* @param key 密钥
* @param iv 初始化向量(CBC模式是必须的)
* @return
*/
private static Cipher init(boolean encrypt, String key, String iv) {
AlgorithmParameters params;
try {
params = AlgorithmParameters.getInstance("AES");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
try {
//设置iv参数
params.init(new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidParameterSpecException e) {
throw new IllegalStateException(e);
}
//我们已经从外部导入了密钥,因此不需要使用KeyGenerator生成随机的密钥
//使用 SecretKeySpec,直接根据字节数组生成一个密钥对象
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher;
try {
//创建Cipher实例,并指定加密算法(AES)、模式(CSC)和填充方式(PKCS7)
cipher = Cipher.getInstance(DEFAULT_ALG_STRING);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("无法获取AES算法实例", e);
} catch (NoSuchPaddingException e) {
throw new IllegalStateException("无法获取算法Padding:" + DEFAULT_ALG_STRING, e);
}
int mode;
if (encrypt) {
//指定操作模式为【加密】
mode = Cipher.ENCRYPT_MODE;
} else {
//指定操作模式为【解密】
mode = Cipher.DECRYPT_MODE;
}
try {
//根据mode的不同,就能判断是加密操作,还是解密操作
cipher.init(mode, secretKeySpec, params);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException(e);
}
return cipher;
}
/**
* @param data 明文数据
* @param key 密钥
* @param iv
* @return
*/
public static String encrypt(String data, String key, String iv) {
if (data == null) {
return null;
}
//初始化一个加密的Cipher
Cipher cipher = init(true, key, iv);
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
byte[] encryptBytes = null;
try {
//执行实际的加密操作
encryptBytes = cipher.doFinal(bytes);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException(e);
}
//使用Base64对字节数组进行加密,获得一个加密数组
byte[] encode = Base64.getEncoder().encode(encryptBytes);
return new String(encode, StandardCharsets.UTF_8);
}
/**
* @param encryptData 加密数据
* @param key 使用加密用到的密钥
* @param iv 使用加密用到的iv
* @return
*/
public static String decrypt(String encryptData, String key, String iv) {
//初始化一个解密的Cipher
Cipher cipher = init(false, key, iv);
if (encryptData == null) {
return null;
}
//通过Base64,先将加密串解密(因为加密数据是通过Base64加密生成的)
byte[] bytes = Base64.getDecoder().decode(encryptData);
byte[] decryptBytes = null;
try {
//执行实际的解密操作
decryptBytes = cipher.doFinal(bytes);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException(e);
}
return new String(decryptBytes, StandardCharsets.UTF_8);
}
}
3.3 创建启动类
public class Demo1 {
public static void main(String[] args) {
String hi = "hello world!";
//AES算法要求的密钥长度有三种
//128位-->密钥长度16字节
//192位-->密钥长度24字节
//256位-->密钥长度32字节
//密钥
String key16= "UMR@202503041014";
//初始化向量iv,固定为16字节
String iv = "9876543211234567";
String encryptData = AESCBCUtil.encrypt(hi, key16, iv);
System.out.println("加密信息:" + encryptData);
System.out.println("解密信息:" + AESCBCUtil.decrypt(encryptData, key16, iv));
}
}
控制台打印结果:
4、为什么要添加Bouncy Castle 库的依赖?
Bouncy Castle 是一个开源加密库,因为JDK8对 PKCS#7 格式不支持,只支持PKCS#5,因此只有导入第三方的加密库,并注册成为JAVA的安全提供者才行。
如果把加密类的静态代码块屏蔽
就会出现这个问题的原因
5、AES128、AES192、AES256的区别
AES加密算法的块大小固定是128位(即16字节),而数字128、192、256指的是密钥(key)的长度,分别对应16字节、24字节、32字节。通常选择AES128就已经有足够的安全性了。
另外,如果是CBC模式,所需的iv和块大小相同。比如,AES的块大小固定是128位,那iv也固定为128位(即16字节),不会随key的长度变化而变化。
如果密钥(key)的字节长度不满足,在加解密时,则会提示以下错误信息
比特即“bit”,还有一种称呼叫作“位”,1字节(byte) = 8bit
但1字符并不一定占用1字节,取决于字符编码。
ASCII编码方式下,每个字符占用 1 字节,包括英文字母、数字、标点符号及一些控制字符。
UTF-8编码方式下,字符长度不固定。ASCII 范围内的字符仍占用1字节,中文占用3字节。
GBK编码方式下,中文字符占2字节
作者:_UMR_