摘要
JDK:1.8.0_202
# 一:PBE
PBE(Password Based Encryption,基于口令加密)算法是一种基于口令的加密算法,特点是没有秘钥的概念,信息交互双方事先拟定好口令即可。单纯的口令很容易通过穷举攻击方式破译,所以PBE也加入了 “盐” 的概念。PBE 算法是对称加密算法的综合性算法,常见算法如 PBEWithMD5AndDES,该算法使用了MD5和DES算法构建PBE算法。
JDK8支持
PBEWithMD5AndDES
、PBEWithMD5AndTripleDES
、PBEWithSHA1AndDESede
、PBEWithSHA1AndRC2_40
、PBEWithSHA1AndRC2_128
、PBEWithSHA1AndRC4_40
、PBEWithSHA1AndRC4_128
、PBEWithHmacSHA1AndAES_128
、PBEWithHmacSHA224AndAES_128
、PBEWithHmacSHA256AndAES_128
、PBEWithHmacSHA384AndAES_128
、PBEWithHmacSHA512AndAES_128
、PBEWithHmacSHA1AndAES_256
、PBEWithHmacSHA224AndAES_256
、PBEWithHmacSHA256AndAES_256
、PBEWithHmacSHA384AndAES_256
和PBEWithHmacSHA512AndAES_256
,其中算法名称中包含 Hmac 的需要配和初始化向量使用。
不需要指定初始化向量的PBE算法族(PBEWithMD5AndDES
、PBEWithMD5AndTripleDES
、PBEWithSHA1AndDESede
、PBEWithSHA1AndRC2_40
、PBEWithSHA1AndRC2_128
、PBEWithSHA1AndRC4_40
、PBEWithSHA1AndRC4_128
)例子:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;
public class PbeTest {
@Test
public void test1() throws Exception {
String value = "helloWorld";
System.out.println("待加密值:" + value);
// 加密算法
String algorithm = "PBEWithSHA1AndDESede";
// 转换模式
String transformation = "PBEWithSHA1AndDESede";
// 密码(口令)
String password = "74Q9gacAnPZZXyiifxdA";
System.out.println("PBE口令:" + password);
// 迭代次数
int count = 99;
// 实例化安全随机数
SecureRandom secureRandom = new SecureRandom();
// 生成盐
byte[] salt = secureRandom.generateSeed(8);
System.out.println("盐值:" + Base64.getEncoder().encodeToString(salt));
// 通过密码生成秘钥材料
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
// 实例化秘钥工厂
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
// 生成秘钥
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
// 实例化PBE参数材料
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, count);
// 实例化密码对象
Cipher cipher = Cipher.getInstance(transformation);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec);
// 加密
byte[] encrypt = cipher.doFinal(value.getBytes());
System.out.println("PBE加密结果:" + Base64.getEncoder().encodeToString(encrypt));
// 解密
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("PBE解密结果:" + new String(decrypt));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
算法名称包含Hmac的PBE算法需要指定初始化向量,比如PBEWithHmacSHA1AndAES_128:
@Test
public void test2() throws Exception {
String value = "helloWorld";
System.out.println("待加密值:" + value);
// 加密算法
String algorithm = "PBEWithHmacSHA1AndAES_128";
// 转换模式
String transformation = "PBEWithHmacSHA1AndAES_128";
// 密码(口令)
String password = "74Q9gacAnPZZXyiifxdA";
System.out.println("PBE口令:" + password);
// 迭代次数
int count = 99;
// 实例化安全随机数
SecureRandom secureRandom = new SecureRandom();
// 生成盐
byte[] salt = secureRandom.generateSeed(8);
System.out.println("盐值:" + Base64.getEncoder().encodeToString(salt));
// 通过密码生成秘钥材料
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
// 实例化秘钥工厂
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);
// 生成秘钥
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
// 创建初始化向量
IvParameterSpec iv = new IvParameterSpec("123456789abcdefg".getBytes());
// 实例化PBE参数材料
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, count, iv);
// 实例化密码对象
Cipher cipher = Cipher.getInstance(transformation);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec);
// 加密
byte[] encrypt = cipher.doFinal(value.getBytes());
System.out.println("PBE加密结果:" + Base64.getEncoder().encodeToString(encrypt));
// 解密
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("PBE解密结果:" + new String(decrypt));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
通过 IvParameterSpec 类创建初始化向量,创建初始化向量的秘钥必须为 16字节,这里为123456789abcdefg,实例化 PBE 参数材料的时候通过构造参数传入初始化向量。
# 二:加密模式
对称加密算法分为:序列密码(流密码)加密,分组密码(块密码)加密 两种。
- 流密码:是对信息流中的每一个元素(一个字母或一个比特)作为基本的处理单元进行加密;
- 块密码:是先对信息流分块,再对每一块分别加密。
之前介绍的都属于块密码加密。不同的算法侧重点不同,有的强调效率,有的强调安全,有的强调容错性。根据数据加密时每个加密区块间的关联方式来区分,可以分为4种加密模式:
- 电子密码本模式(Electronic Code Book,ECB)
- 密文链接模式(Cipher Book Chaining,CBC)
- 密文反馈模式(Cipher Feed Back,CFB)
- 输出反馈模式(Output Feed Back,OFB)
AES 标准除了推荐上述4种工作模式外,还推荐了一种新的工作模式—计数器模式(Counter,CTR)。这些工作模式可适用于各种分组密码算法。
# 2.1 ECB
ECB 模式加解密过程如下图所示:
明文分为若干块,每次加密均产生独立的密文分组,每组的加密结果不会对其他分组产生影响,相同的明文加密后对应产生相同的密文。
- 优点:可并行操作,没有误差传递(因为每个密文都是独立加密来的);
- 缺点:如果明文重复,则对应的密文也会重复,对明文进行主动攻击的可能性较高;
- 用途:适合加密秘钥、随机数等短数据。例如,安全地传递DES秘钥,ECB是最合适的模式。
# 2.2 CBC
CBC 模式加解密过程如下图所示:
明文分为若干块,每次加密前,明文块都和前一个明文块加密后的内容进行异或处理,然后再用秘钥加密。因为第一个明文块没有可以用来异或处理的密文块,所以我们需要提供一个初始化向量来替代。
- 优点:密文链接模式加密后的密文上下文关联,对明文的主动攻击的可能性较低;
- 缺点:不能并行加密,如果在加密过程中发生错误,则错误将被无限放大,导致加密失败。并且需要提供初始化向量;
- 用途:可加密任意长度的数据;适用于计算产生检测数据完整性的消息认证码Mac。
# 2.3 CFB
CFB模式加解密过程如下图所示:
明文分为若干块,每次加密前,先将前一个密文块使用秘钥加密,加密结果和当前明文块异或处理得到密文块。同样的,需要为第一个明文块加密提供初始化向量。
- 优点:和CBC类似;
- 缺点:和CBC类似;
- 用途:因错误传播无界,可用于检查发现明文密文的篡改。
# 2.4 OFB
OFB模式加解密过程如下图所示:
过程和CFB类似,区别在于OFB第一次使用秘钥对初始化向量进行加密(结果为A),加密结果和明文块异或处理得到密文块,下一次操作时候,不是使用秘钥加密前一个密文块,而是使用秘钥加密A的结果再和明文块异或处理,得到当前密文块。
- 优点:和CFB类似;
- 缺点:不利于并行计算;对明文的主动攻击是可能的,安全性较CFB差;
- 用途:适用于加密冗余性较大的数据,比如语音和图像数据。
# 2.5 CTR
CTR模式加解密过程如下图所示:
CTR含义是计数器模式,所以它维护了一个递增的计数器。秘钥加密计数器,结果和明文块异或得到密文块,依次类推。
- 优点:可以并行操作,安全性和CBC一样好;
- 缺点:没有错误传播,因此不易确保数据完整性;
- 用途:适用于各种加密应用。
# 三:填充模式
当需要按块处理的数据, 数据长度不符合块处理需求时, 按照一定的方法填充满块长的规则。如果不填充,待加密的数据块长度不符合要求时程序会抛出异常。
JDK8中主要支持 NoPadding 和 PKCS5Padding 填充模式。
- NoPadding:不填充;
- PKCS5Padding:数据块的大小为8位, 不够就补足。
# 四:加密、填充模式实战
在了解了加密模式和填充模式后,我们回头看前面代码中的transformation参数,实例化Cipher对象的时候需要指定transformation转换模式,转换模式主要有两种格式:
- 算法;
- 算法/加密模式/填充模式。
下面就AES算法来实践不同的加密、填充模式。
当转换模式为AES/ECB/PKCS5Padding时:
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class SymmetryTest {
@Test
public void test1() throws Exception {
String value = "helloWorld";
System.out.println("待加密值:" + value);
// 加密算法
String algorithm = "AES";
// 转换模式
String transformation = "AES/ECB/PKCS5Padding";
// --- 生成秘钥 ---
// 实例化秘钥生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
// 初始化秘钥长度
keyGenerator.init(256);
// 生成秘钥
SecretKey secretKey = keyGenerator.generateKey();
// 生成秘钥材料
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm);
System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded()));
// 实例化密码对象
Cipher cipher = Cipher.getInstance(transformation);
// 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
// 加密
byte[] encrypt = cipher.doFinal(value.getBytes());
System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt));
// 解密
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("AES解密结果:" + new String(decrypt));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
将转换模式改为 AES/CBC/PKCS5Padding
时,程序输出抛出 java.security.InvalidKeyException: Parameters missing
异常。因为该模式需要指定初始化向量,将代码修改为:
@Test
public void test2() throws Exception {
String value = "helloWorld";
System.out.println("待加密值:" + value);
// 加密算法
String algorithm = "AES";
// 转换模式
String transformation = "AES/CBC/PKCS5Padding";
// --- 生成秘钥 ---
// 实例化秘钥生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
// 初始化秘钥长度
keyGenerator.init(256);
// 生成秘钥
SecretKey secretKey = keyGenerator.generateKey();
// 生成秘钥材料
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm);
System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded()));
// 初始化向量,123456789abcdefg初始化向量秘钥,16字节
IvParameterSpec iv = new IvParameterSpec("123456789abcdefg".getBytes());
// 实例化密码对象
Cipher cipher = Cipher.getInstance(transformation);
// 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
// 加密
byte[] encrypt = cipher.doFinal(value.getBytes());
System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt));
// 解密
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] decrypt = cipher.doFinal(encrypt);
System.out.println("AES解密结果:" + new String(decrypt));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
将转换模式改为 AES/CBC/NoPadding
时,程序抛出 javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
异常。因为 helloWorld
不是16字节的倍数。
# 五:手动指定秘钥
在使用对称加密算法加解密的时候,秘钥一般是双方事先约定好的,假如现在通过转换模式为 AES/CTR/PKCS5Padding
加密得到AES秘钥 ZP578BV7JnZ3XdQjLdaDXnhVhBYQ1JRM7CPu9PyMSCg=
,AES密文 WRNzQckpM1mOWg==
,初始化向量秘钥为 123456789abcdefg
,如何通过秘钥和密文进行解密呢,可以参考下面的代码(即演示如何手动指定秘钥并解密)
@Test
public void test3() throws Exception {
String algorithm = "AES";
String transformation = "AES/CTR/PKCS5Padding";
String key = "ZP578BV7JnZ3XdQjLdaDXnhVhBYQ1JRM7CPu9PyMSCg=";
String encrypt = "WRNzQckpM1mOWg==";
String ivKey = "123456789abcdefg";
Cipher cipher = Cipher.getInstance(transformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), algorithm);
IvParameterSpec iv = new IvParameterSpec(ivKey.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(encrypt));
System.out.println("AES解密结果:" + new String(decrypt));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 六:小结
对于对称性算法总结:
算法 | 秘钥长度(位) | 工作模式 | 填充模式 | 初始化向量秘钥长度(字节) |
---|---|---|---|---|
DES | 56 | ECB、CBC、CFB、OFB、CTR等 | NoPadding、PKCS5Padding、ISO10126Padding | 8 |
DESede | 112、168 | ECB、CBC、CFB、OFB、CTR等 | NoPadding、PKCS5Padding、ISO10126Padding | 8 |
AES | 128、192、256 | ECB、CBC、CFB、OFB、CTR等 | NoPadding、 PKCS5Padding、 ISO10126Padding | 16 |
RC2 | 40~1024 | ECB、CBC、CFB、OFB、CTR等 | NoPadding、 PKCS5Padding、 ISO10126Padding | 8 |
RC4 | 40~1024 | ECB | NoPadding | 无 |
Blowfish | 32~448,8的倍数 | ECB、CBC、CFB、OFB、CTR等 | NoPadding、 PKCS5Padding、 ISO10126Padding | 8 |
PBE | 无 | CBC | PKCS5Padding | 16(带Hmac) |