加解密概念
加密
通过一系列计算将明文转换成一个密文。
加密和解密的对象通常是字节数组(有的语言动态数组类比切片)
加密后的数据,可能有很多是不可读字符。通常会将其转换为可见的字符串。
- 直接将字节数组转为16进制的字符串(一个字节8位,4位表示一个16进制数据,因此转换后的数据是转换前字节数组的2倍长度,之所以采用16进制是因为其看起来更加紧凑)
- 将字节数组进行Base64编码
AES加密
算法相关常量在类 Cipher的注释中有说明。
AES是一种对称加密算法。具体加密方式是以16字节 为一块 进行分块加密。
如果明文 长度不是16的倍数,那么则需要进行填充,这就引申出了填充模式。
其密钥长度要求为 128 位(16字节)、192(24字节) 位和 256(32字节) 位,三种,越长越安全,速度越慢
填充模式
常用填充模式:PKCS#5、PKCS#7 。在Java中已经将其行为统一了。
在模式的定义上:
- PKCS#5用于8字节块为单位的加密场景
- PKCS#7用于非8字节块 为单位的加密场景,在现代应用中更通用
但是它们的实现方式都是类似的,剩余的字节数组长度距离加密块差几个字节,就填充几个字节,而且每一位值也是这个长度
比如剩下5字节,在AES中,是以16字节为单位 ,差11个字节。那么就会在这5个字节后面加11个项,而且每一项的值都是11
加密模式
AES中现在用得最多的就是CBC 模式.
这种方式在加密一个块时,需要使用上一个块加密后的数据与当前明文块 进行异或运算。也就是说每一个块的加密都不一样。
这就有一个点,第一个加密块前面没有数据块,所以我们需要指定一个初始向量(有的也称之为偏移量)(其长度就是一个数据块的长度)
示例
- 加密(如果使用的是CBC模式,则需要指定初始向量(说白了就是手动创建加密块,因此也是长度为16的字节数组),)
- 生成iv(如果是CBC模式,则需要。长度与加密块一致(16字节))
java
// 如果是CBC模式 首先生成iv向量(本质就是一个长度为16的字节数组,随便怎么构建)
// 可以自行创建16字节的数组,但推荐生成随机iv
// 自行构建
String ivStr = "1111111111111111"
byte[] iv = ivStr.getBytes(StandardCharsets.UTF_8);
// 生成随机iv
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
- 生成密钥
AES密钥支持16字节、24字节、32字节(越长越安全,运算速度越慢)
与iv类似,可以自定义密钥串,然后构建密钥对象,也可以直接生成指定长度的随机密钥
java
// 指定密钥串,构建密钥对象
String key = "0111111111111111";
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
// 生成指定长度的密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
- 创建加密器,在java中通过聚合名称指定多项配置(AES/CBC/PKCS7Padding )它制定了加密算法、加密模式、填充模式
java
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
- 初始化加密器。指定加密还是解密,密钥,初始向量
java
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
- 执行加解密。参数和返回都是字节数组
java
byte[] resByte = cipher.doFinal(s.getBytes(StandardCharsets.UTF_8));
- 以上加密结果可能会有一些不可读的字符,因此为了方便查看,存储,传输等,我们常常将字节数组转为16进制字符串 或者Base64 进行存储,传输。
不仅是加密结果。随的的生成iv字节数组,随机生成密钥字节数组 等包含不可读字符的字节数组都可以采用这种方式进行分发,存储
- Base64编解码
java
// 字节数组编码为Base64字符串
String encodedKey = Base64.getEncoder().encodeToString(encoded);
// Base64字符串解码为字节数组
byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
- 16进制编解码
java
// 字节数组编码为16进制字符串
public String byte2Hex(byte[] bytes) {
int len = bytes.length;
StringBuilder builder = new StringBuilder();
for (int i = 0, j = 0; i < len; i++) {
builder.append(Integer.toHexString((0xF0 & bytes[i]) >>> 4));
builder.append(Integer.toHexString(0x0F & bytes[i]));
}
return builder.toString();
}
// 16进制字符串转字节数组
public byte[] hexToBytes(String s) {
byte[] bytes = new byte[(s.length() + 1) >> 1];
for (int i = 0, j = 0; i < bytes.length; i++) {
int left = Character.digit(s.charAt(j++), 16) << 4;
left = left | Character.digit(s.charAt(j++), 16);
bytes[i] = (byte) (left & 0xff);
}
return bytes;
}