AES 对称密码和分组工作模式

先看一个在 Node.js 中使用 AES 对文件内容进行加密的例子:

const fs = require('fs')
const { createCipheriv, randomBytes } = require('crypto')

const key = randomBytes(32)
const iv = randomBytes(16)
const cipher = createCipheriv('aes-256-cbc', key, iv)

// 加密文件
fs.createReadStream('plain.txt')
.pipe(cipher)
.pipe(fs.createWriteStream('aes-256-cbc.dat'))

// 输出 key,iv
fs.writeFileSync('aes-256-cbc.key', key.toString('hex') + '|' + iv.toString('hex'))

输出的 key 和 iv 分别为:

435157a4775085cef2aa2d44dd399eeea4a3cc304f85c0ef895770943e5ec4fc
294926351aad0cc4e8ce88c8356575b5

对加密后的文件进行解密:

const fs = require('fs')
const { createDecipheriv } = require('crypto')

const [key, iv] = fs.readFileSync('aes-256-cbc.key', {encoding: 'utf8'}).split('|')
const decipher = createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), Buffer.from(iv, 'hex'))

fs.createReadStream('aes-256-cbc.dat')
.pipe(decipher)
.pipe(fs.createWriteStream('aes-256-cbc.txt'))

如果你感兴趣,可以把代码复制出来跑一下看看。

接下来我并不想继续介绍 Node.js 中的加密、解密 API 如何,这些在 Node.js 官方文档中都有:

Crypto - Node.js v12.13.0 Documentation

在上面的例子,使用了算法 aes-256-cbc 进行加解密。AES 是一种对称密码算法,而后面的 256cbc 是指什么?iv 又是什么呢?

如果你对这感兴趣,那么本文正适合你。

AES 对称密码

AES 全称为“Advanced Encryption Standard”,是美国政府于 2001 年通过公开竞标征集的对称密码算法,用于替代已经老旧的 DES 算法。

AES 有三种密钥长度:128,192 和 256 比特。上面例子中的 256 就表示密钥长度为 256 比特。

由于 AES 算法是公开的,所以决定加密数据的机密性的关键是 密钥。密钥越长,则可供选择的组合数目越多,也就是密钥空间越大,对于暴力破解来说难度也就越高。

AES 每次加密的数据块大小是确定的 128 比特,如果数据超过这个大小,就需要进行拆分,分别进行加密后再组装,解密的过程则相反。

看起来对数据进行分组加密再组合是很简单的事情,最多额外约定下如果分组数据不够 128 比特如何填补就好了。

分组工作模式

ECB 模式

前面提到的思路,就是 ECB(Electronic CodeBook)模式的实现方式:

AES 对称密码和分组工作模式_第1张图片
EBC 模式加密过程

解密过程相反:分解 - 解密 - 合并。

但是这种模式是不够安全的,我们考虑数据的发送方(Alice)和接收方(Bob)之间有一个攻击者(Mallory)的情况。

由于对称密码的存在,Mallory 并不能解密数据。但是 ECB 模式下,数据只是简单分组加密再组装,Mallory 如果改变密文分组的顺序,Bob 解密得到的明文也就被改变了。

如果 Mallory 知晓通信报文的格式,就可以通过调整密文分组的顺序达到特殊的目的。例如 Alice 发送转账数据给 Bob,Mallory 从中对调了付款人和收款人信息对应的分组,就会导致 Bob 接收到错误数据。

能够在不破译密文的情况下操纵明文,这是 ECB 模式的一大弱点。

接下来,我们看下 CBC 模式是怎么应对这种攻击的。

CBC 模式

CBC 全称 Cipher Block Chaining,也就是密文分组链接模式:

AES 对称密码和分组工作模式_第2张图片
CBC 模式加密过程

上一个分组加密结果先与下一个分组明文执行 XOR(异或)运算,然后再加密。由于第一个明文分组没有上一个密文分组可以使用,所以需要事先传人一个数据,也就是 IV(初始化向量),其长度与分组的大小相同。对于 AES 密码算法,其分组大小为 128 bit,所以使用的 IV 也就是 128 比特。

采用 CBC 模式,密文中如果有一个分组缺失,后续所有分组都无法正确解密;如果一个分组数据被篡改或损坏,由于链接关系的存在,会影响下一个分组的解密(因为解密需要使用上一分组的密文进行 XOR 运算)。

如果 Mallory 对分组顺序进行了调换,由于相邻分组链接的关系,会导致无法正常解密。

当然,确保数据在传输过程中不被篡改(完整性),使用消息认真码(MAC,Message Authentication Code)是更好的方案。

根据前一分组信息介入的时机和方式的不同,还有其他几种和 CBC 模式类似的模式。

CFB 模式

CFB,Cipher FeedBack,密文反馈模式:

AES 对称密码和分组工作模式_第3张图片
CFB 模式加密过程

看起来有点奇怪,第一个分组中,应用 AES 进行加密的其实 IV,IV 的密文再和明文进行 XOR 运算得到分组最终密文。其实 XOR 也是一种加密运算,而 IV 的密文则可以看作是生成的 一次性密码本

OFB 模式

OFB,Output-FeedBack,输出反馈模式:

AES 对称密码和分组工作模式_第4张图片
OFB 模式加密过程

和 CFB 模式非常类似,只是提供下一组的密钥是密码算法的输出,而非最终的密文。

OFB 的优势是可以抛开明文,事先计算好每个分组需要的密钥,所以生成密钥流和对明文进行 XOR 运算是可以并行的。

CTR 模式

CTR,CounTer,计数器模式:

AES 对称密码和分组工作模式_第5张图片
CTR 模式加密过程

CTR 模式和 CBC 模式差别就更大了,CTR 模式是通过对逐次累加的计数器进行加密来生成密钥流的 流密码

CTR 和 OFB 有点类似,真正对明文加密的过程,是基于密钥流进行 XOR 运算。而 CTR 模式更进一步,由于是采用计数器的模式,任意分组的密钥是可以提前计算的,不依赖前一分组,所以在支持并行计算的系统中,CTR 模式的速度会非常快。

小结

这里介绍的分组密码模式有很多,一般而言,ECB 模式是不应该使用的,而通常使用的是 CBC 模式和 CTR 模式。

认证加密模式

如果在加密过程中,同时还生成了有关数据的认证信息,则既可以确保数据的机密性,又能确保完整性。

认证加密的方法有:

  • Encrypt-then-MAC (EtM):明文加密,密文生成 MAC
  • Encrypt-and-MAC (E&M):明文加密,明文生成 MAC
  • MAC-then-Encrypt (MtE):明文生成 MAC,明文和MAC一起加密

Node.js 文档中给出的一个使用 aes-192-ccm 的例子,CCM 模式就是一个满足认证加密要求的算法:

crypto_ccm_mode - crypto

总结

回过头,再看下本文开头的算法 aes-256-cbc,我们知道:

  • AES:对称密码算法
  • 256:选择的 AES 密钥长度
  • CBC:分组密码模式

在使用该算法进行加密时,需要传入两个参数:

  • key:AES 的密钥,256 比特
  • iv:CBC 模式需要的初始化向量,与分组大小一致,AES 分组为固定的 128 比特

另外,密钥 key 是重要的机密,要妥善保存,通信双方如果需要传输该密钥,需要额外通过其他机制(密钥交换/协商机制)。而初始化向量 iv 则无需秘密保存,但要避免在反复使用相同的数据,尽量每次创建随机数据。

你可能感兴趣的:(AES 对称密码和分组工作模式)