对称加密
# 概念
对称加密(也叫私钥 (opens new window)加密)指加密和解密使用相同密钥 (opens new window)的加密算法。有时又叫传统密码算法 (opens new window),就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法 (opens new window)中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。
从程序的角度看,所谓加密,就是这样一个函数,它接收密码和明文,然后输出密文:
secret = encrypt(key, message);
而解密则相反,它接收密码和密文,然后输出明文:
plain = decrypt(key, secret);
算法 | 密钥长度 | 工作模式 | 填充模式 | 介绍 |
---|---|---|---|---|
DES | 56/64 | ECB/CBC/PCBC/CTR/... | NoPadding/PKCS5Padding/... | 已破解,不再安全,基本没有企业在用了 是对称加密算法的基石,具有学习价值 |
3DES | 112/128/168/192 | ECB/CBC/PCBC/CTR/... | NoPadding/PKCS5Padding/... | 早于AES出现来替代DES 计算密钥时间太长、加密效率不高,所以也基本上不用 |
AES | 128/192/256 | ECB/CBC/PCBC/CTR/... | NoPadding/PKCS5Padding/PKCS7Padding/... | 最常用的对称加密算法 密钥建立时间短、灵敏性好、内存需求低 |
IDEA | 128 | ECB | PKCS5Padding/PKCS7Padding/ | 常用的电子邮件加密算法 工作模式只有ECB |
# 工作模式
# 流加密与块加密
(1)分组加密,也叫块加密(block cyphers),一次加密明文中的一个块。是将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。具有代表性的块加密算法有DES,AES,3DES等。
(2)序列加密,也叫流加密(stream cyphers),一次加密明文中的一个位。是指利用少量的密钥(制乱元素)通过某种复杂的运算(密码算法)产生大量的伪随机位流,用于对明文位流的加密。 解密是指用同样的密钥和密码算法及与加密相同的伪随机位流,用以还原明文位流。
序列加密算法中,有 RC4、ChaCha、ORYX、SEAL等。其中 RC4 采用 OFB 工作模式。
分组加密算法中,有 AES、3DES、DES、IDEA等。
# ECB
Electronic Code Book 又称电子密码本模式:Electronic codebook,是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
DES对称加密算法的 ECB模式 其实非常简单,就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,按照需求补足8个字节进行计算,之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。
特点:
- 简单,有利于并行计算,误差不会被传送;
- 同样的明文块会被加密成相同的密文块,不能很好的隐藏明文数据模式;
- 加密消息块相互独立成为被攻击的弱点,不能提供严格的数据保密性,可以对明文进行主动攻击;
# CBC
密码分组链接(CBC,Cipher-block chaining)模式,每个明文块先与前一个密文块进行异或后,再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量IV。它的实现机制使加密的各段数据之间有了联系。
特点:
不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准;
每个密文块依赖于所有的信息块,明文消息中一个改变会影响所有密文块;
发送方和接收方都需要知道初始化向量;
主要缺点是加密过程是串行的,无法被并行化,因为在加密时,明文中的微小改变会导致其后的全部密文块发生改变,而且消息必须被填充到块大小的整数倍。而在解密时,从两个邻接的密文块中即可得到一个明文块。因此,解密过程可以被并行化,而解密时,密文中一位的改变只会导致其对应的明文块完全改变和下一个明文块中对应位发生改变,不会影响到其它明文的内容。
# PCBC
填充密码块链接(PCBC,Propagating cipher-block chaining)或称为明文密码块链接(Plaintext cipher-block chaining),是一种可以使密文中的微小更改在解密时导致明文大部分错误的模式,并在加密的时候也具有同样的特性。
对于使用PCBC加密的消息,互换两个邻接的密文块不会对后续块的解密造成影响。
# CFB
密文反馈(CFB,Cipher feedback)模式与ECB和CBC模式只能够加密块数据不同,可以将块密码变为自同步的流密码;CFB的解密过程几乎就是颠倒的CBC的加密过程。
# OFB
OFB模式(输出反馈:Output feedback),OFB是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流;解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。
OFB与CFB一样都非常适合对流数据的加密,OFB由于加密和解密都依赖与前一段数据,所以加密和解密都不能并行。然而,由于明文和密文只在最终的异或过程中使用,因此可以事先对IV进行加密,最后并行的将明文或密文进行并行的异或处理。
可以利用输入全0的CBC模式产生OFB模式的密钥流。这种方法十分实用,因为可以利用快速的CBC硬件实现来加速OFB模式的加密过程。
# CTR
CTR模式(Counter mode,CM)也被称为ICM模式(Integer Counter Mode,整数计数模式)和SIC模式(Segmented Integer Counter),与OFB相似,CTR将块密码变为流密码。它通过递增一个加密计数器以产生连续的密钥流,其中,计数器可以是任意保证长时间不产生重复输出的函数,但使用一个普通的计数器是最简单和最常见的做法。
CTR模式的特征类似于OFB,但它允许在解密时进行随机存取。由于加密和解密过程均可以进行并行处理,CTR适合运用于多处理器的硬件上。
# 推荐阅读
加密模式ECB、CBC、CFB、OFB、CTR (opens new window)
# 代码实现
# AESECB
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* @author blog.unclezs.com
* @since 2022/5/18 10:45
*/
public class ECBSample {
public static void main(String[] args) throws Exception {
// 原文
String message = "Hello, world!";
System.out.println("原文: " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes(StandardCharsets.UTF_8);
// 加密
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(key, data);
System.out.println("加密: " + Base64.getEncoder().encodeToString(encrypted));
// 解密
byte[] decrypted = decrypt(key, encrypted);
System.out.println("解密: " + new String(decrypted, StandardCharsets.UTF_8));
}
/**
* 加密
*
* @param key 关键
* @param input 输入
* @return {@link byte[]}
* @throws GeneralSecurityException 一般安全例外
*/
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(input);
}
/**
* 解密
*
* @param key 关键
* @param input 输入
* @return {@link byte[]}
* @throws GeneralSecurityException 一般安全例外
*/
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
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
46
47
48
49
50
51
52
53
54
55
56
57
58
输出
原文: Hello, world!
加密: 2xiGROlFBhC57b7EGu5c3g==
解密: Hello, world!
2
3
# AESCBC
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @author blog.unclezs.com
* @since 2022/5/18 10:47
*/
public class CBCSample {
public static void main(String[] args) throws Exception {
// 原文
String message = "Hello, world!";
System.out.println("原文: " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".getBytes(StandardCharsets.UTF_8);
// 加密
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encrypt(key, data);
System.out.println("密文: " + Base64.getEncoder().encodeToString(encrypted));
// 解密
byte[] decrypted = decrypt(key, encrypted);
System.out.println("原文: " + new String(decrypted, StandardCharsets.UTF_8));
}
/**
* 加密
*
* @param key 关键
* @param input 输入
* @return {@link byte[]}
* @throws GeneralSecurityException 一般安全例外
*/
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = 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, keySpec, ivps);
byte[] data = cipher.doFinal(input);
// IV不需要保密,把IV和密文一起返回:
return join(iv, data);
}
/**
* 解密
*
* @param key 关键
* @param input 输入
* @return {@link byte[]}
* @throws GeneralSecurityException 一般安全例外
*/
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
68
69
70
71
72
73
74
75
76
77
78
79
输出
原文: Hello, world!
密文: wD4+NFIGjN820zwud6ym/yFccHuX7nqcNBZzPLQ+sYo=
原文: Hello, world!
2
3