图解密码技术-CBC模式

分组密码(Block Cipher)是一种密码对称加密算法,特点是按照固定长度的块(block)来对数据进行加密和解密。

常见的分组密码以及特点

常见的分组密码有DES、AES、SM4、Blowfish等。

分组密码的特点

  1. 分组长度固定,常见的块长度有64位、128位、256位等
  2. 可以将数据进行分组,切割为不同的数据块,每一块数据单独处理加密
  3. 使用对称密钥,加解密使用的是同一个密钥
  4. 加解密算法是确定的,相同的明文、密钥和加密算法会产生相同的密文

分组密码的工作模式

分组密码算法只能加密固定长度的分组,但是我们需要加密的明文可能会超过分组密码分组的长度,这时候就需要对明文进行切割,分成几组分别进行加密,直到所有的明文加密完成。这种方式就称之为分组密码的模式

分组密码的主要模式

  1. ECB模式:(Electronic CodeBook mode) 电子密码本模式
  2. CBC模式:(Cipher Block Chaining mode) 密码分组链接模式
  3. CFB模式:(Cipher FeedBack mode) 密文反馈模式
  4. OFB模式:(Output FeedBack mode)输出反馈模式
  5. CTR模式:(CounTeR mode) 计数器模式

ECB模式

书中介绍ECB模式是将明文直接分组,然后进行分别加密处理。如图:

image-20230809225412547

从图中可以直观的看出来,明文的分组和密文的分组是完全一致的。在EDB模式中,明文分组和密文分组是一一对应关系,那么如果明文中有两组以上相同的内容那么这些密文也会一样。所以,ECB模式是不安全的。

Java代码实现ECB加密

1
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
public static void main(String[] args) throws Exception {
// 创建一个AES密钥
SecretKeySpec key = new SecretKeySpec("1234567890123456".getBytes(StandardCharsets.UTF_8), "AES");

// 创建一个AES/ECB/PKCS5Padding模式的Cipher
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

// 初始化Cipher为加密模式,设置密钥
cipher.init(Cipher.ENCRYPT_MODE, key);

// 对"Hello World"字符串进行加密
byte[] encrypted = cipher.doFinal("Hello World".getBytes(StandardCharsets.UTF_8));

// 将加密后的数据进行Base64编码,方便打印
String encryptedBase64 = Base64.getEncoder().encodeToString(encrypted);
System.out.println("Encrypted: " + encryptedBase64);

// 初始化Cipher为解密模式,设置密钥
cipher.init(Cipher.DECRYPT_MODE, key);

// 对加密后的数据进行解密
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedBase64));

// 打印解密后的字符串
System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8));
}

对ECB模式的攻击

举一个例子:

假设我们有一个转账的业务,转账的数据由三部分组成;

分组A = 付款人小明

分组B=收款人小红

分组C=金额

那么使用ECB模式加密和被攻击的动作如下:

image-20230809230257293

如上图,黑客只是简单的调换了密文的顺序,并没有破译密文,就已经达到攻击的目的。

原本由小明付款,现在变成了小红付款

在ECB模式中,只要将任意密文调换位置,相应的明文也会变换位置,替换密文,明文也会被替换,删除密文,明文也会被删除。所以ECB模式是不安全的。

CBC模式

CBC模式是将前一个密文分组与当前明文分组的内容混合起来进行加密,如图

image-20230809231050229

在CBC模式中,首先将明文分组与前一个密文分组进行异或运算,然后再进行加密。那么,这里会有一个问题,就是第一个明文分组应该与谁进行异或运算呢?

初始化向量

当第一个明文加密这时候并不存在“前一个密文分组”。所以,我们需要准备一个初始化值,这个值的长度与分组比特位一致。我们成这个初始化值为初始化向量(Initialization Vector),通常简写为IV。

异或运算

异或是一种逻辑运算,对应的逻辑符号是 ⊕;如下表格表示了运算结果

A B A ⊕ B
0 0 0
0 1 1
1 0 1
1 1 0

异或运算有以下几个特点:

  1. 当两个输入相同时,输出为0。当两个输入不同时,输出为1。
  2. 异或运算满足交换律和结合律。
  3. 一个数异或它本身结果为0。
  4. 一个数异或0等于本身。

CBC模式的特点

因为明文分组在加密之前会和初始化向量进行异或运算,那么即使明文分组1和明文分组2的值是一样的,那么加密结果也会不同。例如:

明文分组1 = 001

明文分组2 = 001

初始化向量 = 000

明文分组1与初始化向量运算结果 001 ⊕ 000 = 001

明文分组1与明文分组2运算结果 001 ⊕ 001 = 000

因为分组1需要跟初始化向量进行异或,得到的结果必然是不同的。

Java实现CBC加密

1
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
public static void main(String[] args) throws Exception {
// 初始化向量,用于CBC模式的加密
byte[] iv = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
IvParameterSpec ivspec = new IvParameterSpec(iv);

// 创建一个AES密钥
SecretKeySpec key = new SecretKeySpec(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, "AES");

// 创建一个AES/CBC/PKCS5Padding模式的Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

// 初始化Cipher为加密模式,设置密钥和初始化向量
cipher.init(Cipher.ENCRYPT_MODE, key, ivspec);

// 对"Hello World"字符串进行加密
byte[] encrypted = cipher.doFinal("Hello World".getBytes());

// 初始化Cipher为解密模式,设置密钥和初始化向量
cipher.init(Cipher.DECRYPT_MODE, key, ivspec);

// 对加密后的数据进行解密
byte[] decrypted = cipher.doFinal(encrypted);

// 打印解密后的字符串
System.out.println(new String(decrypted));
}

对CBC模式的攻击

攻击初始化向量

在加密时CBC模式需要一个初始化向量。这个数据必须是一个安全的、不可预测的随机数列。如果生成的初始化向量随机度不高,那么两个明文使用了相同的IV加密,两个密文的第一个块将是相同的。这样就会计算出第一个密文块,并且解密所有后续的块。