对称加密算法
# 对称加密算法
对称加密算法就是传统的用一个密码进行加密和解密。
# 对称加密算法的应用
例如我们用压缩包工具,对文件进行压缩时,可以选择用一个密码进行加密(加密算法选默认的即可):
而解压压缩包的时候,我们需要输入密码,才能解压缩。
从程序的角度来看,可以简单地认为,加密就是通过加密算法和密码(密钥),对文件进行操作,生成加密后的文件(也叫密文)
secret = encrypt(key, message);
而解密则相反,它接收密码和密文,然后输出明文:
plain = decrypt(key, secret);
由于加密和解密使用同样的规则和"密钥",这被称为"对称加密算法"(Symmetric-key algorithm)。
# 常见的对称加密算法
在软件开发中,常用的对称加密算法有:
算法名称 | 密钥长度 | 工作模式 | 填充模式 |
---|---|---|---|
DES | 56/64 | ECB/CBC/PCBC/CTR/... | NoPadding/PKCS5Padding/... |
AES | 128/192/256 | ECB/CBC/PCBC/CTR/... | NoPadding/PKCS5Padding/PKCS7Padding/... |
IDEA | 128 | ECB | PKCS5Padding/PKCS7Padding/... |
密钥长度直接决定加密强度,而工作模式和填充模式可以看成是对称加密算法的参数和格式选择。
加密数据的时候,我们通常会对数据进行分组(例如 128bit 一组),然后再进行加密。因为要加密的数据可大可小(例如一个蓝光电影和一个小的 txt 文件),分组后方便处理。而工作模式就是如何处理这些分组,常见的工作模式如下(了解即可):
- ECB(Electronic CodeBook)模式,即电子密码本模式。该模式是将明文分组,加密后直接成为密文分组,分组之间没有关系。
- CBC(Cipher Block Chaining)模式,即密码分组链接模式。该模式首先将明文分组与前一个密文分组进行 XOR 运算,然后再进行加密。只有第一个明文分组特殊,需要提前为其生成一个与分组长度相同的比特序列,进行 XOR 运算,这个比特序列称为初始化向量(Initialization Vector),简称 IV。
- CFB(Cipher FeedBack)模式,即密文反馈模式。该模式首先将前一个密文分组进行加密,再与当前明文分组进行 XOR 运算,来生成密文分组。CFB 模式也需要一个 IV。
- CTR(CounTeR)模式,即计数器模式。该模式也会产生一个密钥流,它通过递增一个计数器来产生连续的密钥流。对该计数器进行加密,再与明文分组进行 XOR 运算,计算得出密文分组。
- ...............
注意,ECB 模式是最简单的加密模式,它只需要一个固定长度的密钥,固定的明文会生成固定的密文,这种一对一的加密方式会导致安全性降低,更好的方式是通过 CBC 模式,它需要一个随机数作为 IV 参数,这样对于同一份明文,每次生成的密文都不同。
分组后(例如 128bit 一组),可能会出现最后一组不符合分组长度,此时需要按一定的方式,将尾部明文分组进行填充,这种将尾部分组数据填满的方法称为填充(Padding)。常见的填充模式如下(了解即可):
- No Padding:即不填充,要求明文的长度,必须是加密算法分组长度的整数倍。
- ANSI X9.23:在填充字节序列中,最后一个字节填充为需要填充的字节长度,其余字节填充 0。
- PKCS5 和 PKCS7:在填充字节序列中,每个字节填充为需要填充的字节长度。
- .....
Java 标准库提供的算法实现并不包括所有的工作模式和所有填充模式,但是通常我们只需要挑选常用的使用就可以了。
注意,DES 算法由于密钥过短,以目前的计算机水平可以在短时间内暴力破解密文,所以现在已经不安全了。
# 在 Java 中使用 AES 加密
Java 标准库提供的对称加密接口非常简单,使用时按以下步骤编写代码:
- 根据算法名称/工作模式/填充模式获取 Cipher 实例;
- 根据算法名称初始化一个 SecretKey 实例,密钥必须是指定长度;
- 使用 SerectKey 初始化 Cipher 实例,并设置加密或解密模式;
- 传入明文或密文,获得密文或明文。
示例:
package chapter12Hash;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.util.Base64;
public class EncryptDemo1AES {
public static void main(String[] args) throws Exception {
// 原文
String message = "Hello World!";
System.out.println("message: " + message);
// 128位密钥 = 16 bytes key
byte[] key = "1234567890abcdef".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encryptedData = encrypt(key, data);
System.out.println("Encrypted Data: " + Base64.getEncoder().encodeToString(encryptedData));
// 解密
byte[] decryptedData = decrypt(key, encryptedData);
System.out.println("Decrypted Data: " + new String(decryptedData, "UTF-8"));
}
// 加密
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpce = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpce);
return cipher.doFinal(input);
}
//解密
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpce = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpce);
return cipher.doFinal(input);
}
}
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
运行结果:
message: Hello World!
Encrypted Data: N0N4UtmbNnVfSdUE/Nk/rw==
Decrypted Data: Hello World!
2
3
# 使用 CBC 模式
由于 ECB 模式没那么安全,我们可以改为使用 CBC 模式,示例如下:
package chapter12Hash;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptDemo2CBC {
public static void main(String[] args) throws Exception {
// 原文
String message = "Hello World!";
System.out.println("message: " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encryptedData = encrypt(key, data);
System.out.println("Encrypted Data: " + Base64.getEncoder().encodeToString(encryptedData));
// 解密
byte[] decryptedData = decrypt(key, encryptedData);
System.out.println("Decrypted Data: " + new String(decryptedData, "UTF-8"));
}
// 加密
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKey keySpce = new SecretKeySpec(key, "AES");
// CBC模式需要生成一个16 bytes的initialization vector:
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] iv = sr.generateSeed(16);
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpce, ivps);
byte[] data = cipher.doFinal(input);
return join(iv, data);
}
//解密
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException{
// 把input分割成IV和密文:
byte[] iv = new byte[16];
byte[] data = new byte[input.length - 16];
System.arraycopy(input, 0, iv, 0, 16);
System.arraycopy(input, 16, data, 0, data.length);
// 解密:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
return cipher.doFinal(data);
}
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r = new byte[bs1.length + bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
}
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
55
56
57
58
59
60
61
62
63
64
65
66
67
在 CBC 模式下,需要一个随机生成的 16 字节 IV 参数,必须使用 SecureRandom
生成。因为多了一个 IvParameterSpec
实例,因此,初始化方法需要调用 Cipher
的一个重载方法并传入 IvParameterSpec
。
观察输出,可以发现每次生成的 IV 不同,密文也不同。
# 对称加密算法的缺点
1976 年以前,所有的加密方法都是使用的的对称加密:
- 发送方选择某一种加密规则,对信息进行加密;
- 接收方使用同一种规则,对信息进行解密。
对称加密算法使用的是一个密钥,在单钥加密的情况下,密钥只有一把,所以密钥的保存变得很重要。一旦密钥泄漏,密码也就被破解。而我们想要接收方能解密的话,又必须给他密钥,那么问题来了,怎么保证密钥在保存和传输过程中不被泄漏呢?很难很难,因为我们要加密才能传送信息,但是在密钥给到对方之前,又不能加密。因此通常都是见面协商密钥。
多个密钥的管理也会带来巨大的成本。每和一个用户通信,就得用一个新的密钥。如果都用一个密钥会有什么后果?举例张三和李四用密钥 A,张三和王五也用密钥 A 的话,那么张三和王五的通信,可以被李四解密的。
现实生活中也有这样的例子:笔者有多个银行卡,每个银行卡的密码都是不同的,以至于笔者经常忘记一些少用的银行卡的密码;
此外,笔者使用过知乎,B 站,CSDN,GitHub,Gitee 等网站,这些网站的密码也是不同的…… 更别说手机上那一大堆 APP 了(当然,现在可以短信登录,方便了一点)。
# 参考
对称加密算法 - 廖雪峰的官方网站 (opens new window)
一文搞懂对称加密:加密算法、工作模式、填充方式、代码实现_教 IT 的无语强的博客-CSDN 博客 (opens new window)