AES加密: GCM和CBC模式的区别
Difference between GCM and CBC
导语
项目代码的构建扫描结果中,Sonarsource Rule 中建议用 AES - GCM 来替代 AES-CBC,这两者究竟有什么区别呢?可以按照建议替换掉么?趁着这个机会,学习了一下常用的AES算法基础概念,以及CBC模式和GCM模式,并使用了JMH做了benchmark ~
背景
一,主要区别
- AES-GCM可以并行加密解密,AES-CBC的模式决定了它只能串行地进行加密。因为加密是耗时较久的步骤,且加密的方式是相同的,所以并行地实现AES-GCM算法的时候,其效率是高于AES-CBC的;
- AES-GCM提供了GMAC信息校验码,用以校验密文的完整性。AES-CBC没有,无法有效地校验密文的完整性;
- AES-GCM是流加密的模式,不需要对明文进行填充。AES-CBC是块加密的模式,需要对明文进行填充。(AES-GCM中进行AES加密的是counter,AES-CBC中进行AES加密的是明文块);
- 由于AES-CBC中必须要用到padding,导致最后一个明文块与其他密文块不同,因此可能会受到padding Oracle attacks,从而可以直接通过初始向量IV和密码,即可得到明文。
二,ASE介绍
AES属于对称加密算法。在实际中,一般是通过RSA加密AES密钥,进行密钥的传输。以下将简要地介绍AES加密的一些概念和主要步骤。
分组密码
AES为分组密码,即将明文分成一组一组的,每组长度相等。在AES的标准规范中,分组的长度是128位。密钥的长度可以为128位,192位或256位。密钥的长度不同,推荐的加密轮数也不同:
加密模式
因为AES是分组加密,分组加密只能加密固定长度的分组,而实际需要加密的明文可能超过分组长度,此时就需要加密模式,以完成对整个明文的加密。常见的加密模式如下:
初始向量
初始向量IV的作用和MD5的加盐类似,目的是防止同样的明文块,始终加密成同样的密文块。以CBC模式为例如下:
CBC模式在每一个明文块加密前,会让明文块和一个值先做异或操作。IV作为初始化向量,参与第一个明文块的异或。后续的每一个明文块和它前一个明文块所加密出的密文块相异或,从而保证加密出的密文块都不同
填充方式
由于AES加密只能对固定长度的输入数据进行处理,而数据长度通常是可变的,因此需要对最后一个数据块做填充处理。在加密前,对数据进行填充。填充的模式有PKCS5, PKCS7, NOPADDING。
AES总体流程
AES加密的整个过程是在一个4*4的字节矩阵State上进行的。这个字节矩阵是由当前的明文块处理得到的。同样,加密密钥也需要做同样的变换得到拓展密钥的矩阵。
AES算法主要有四种操作处理:1-密钥加法AddRoundKey,2-字节替换SubByte,3-行位移层ShiftRows,4-列混淆层MixColumn。解密的时候,按照流程进行相应的逆运算即可。AES算法的四个操作处理都是在GF(Galois Field)域上进行运算的。
AddRoundKey
即是将当前的矩阵State和拓展密钥进行按位异或。
SubBytes
字节变换即是将矩阵State中的每一个字节,查找其在变换数组S_box中的对应值,替换成新字节即可。解密的时候有对应的逆S_box。
S盒和逆S盒实现的是下述流程:
ShiftRows
MixColumn
列混淆操作是AES中主要的扩散元素,它混淆了输入矩阵的每一列,使得输入的每个字节都会影响到4个输出字节。
密钥拓展
密钥拓展中主要使用了一个G函数,可以增强密钥编排中的非线性,并消除AES中的对称性,从而更好地抵抗某些分组密码攻击。
三,CBC模式
CBC模式介绍
上图为CBC模式。AES-CBC中block cipher encryption即是使用了AES加密。CBC使用了一个初始向量IV,和明文块进行异或,用以加盐。后续每一个明文块,都与上一个生成的密文块进行异或,从而避免相同的明文块加密出的内容是相同的。
对当前密文块进行解密的时候,需要先对当前密文块进行AES解密得到A,因为已知上一个密文块B,此时只需要A异或B,即可得到当前密文块的明文C。
从上面的图可知,CBC模式只能进行串行加密,因为每一个明文块的加密,都依赖于上一个明文块的加密结果。解密的过程是可以并行进行的。
同时因为使用了密码块当作盐,所以如果有某一块加密错误,则会造成错误传播的现象。
CBC中必须填充,从而造成了最后一个明文块与其他明文块的不同。给攻击造成了机会。因此CBC模式是可能受到padding oracle attacks的。
四,GCM模式
GCM(GMAC Counter Mode),使用了Counter的模式,并且带有GMAC消息认证码。
CTR模式
因为GCM是基于CTR(Counter Mode)的,所以以下介绍CTR Mode。
从上图可知,在CTR模式中,不再对明文进行加密,而是对一个逐次累加的计数器进行加密,用加密后得到的比特序列与明文分组进行异或,得到每一个明文块的密文。计数器的初始值,是依据于一个nonce的。
因为计数器是递增的,所以加密得到的比特序列是不同的,再与明文进行异或,可以避免相同的明文块被加密成一样的密文块。
因为每一个明文块的加密都是独立的,不依赖于其他明文块,所以CTR模式的AES加密是可以并行地对明文块进行加密的,并且不会有错误传播的现象。
以下为CTR解密模式:
CTR的解密也是较为直接的。直接对计数器进行加密,得到比特序列,再和对应的密文块进行异或,就可以得到相应的明文块。
因为CTR模式下的AES加密是针对计数器进行加密的,所以CTR模式是不需要padding的。
MAC和GMAC
CBC和CTR模式都不能校验消息的完整性。依据普通hash不能保证消息的完整性,因为篡改者截获原始密文消息时,先篡改密文,而后计算篡改后密文的hash,替换原有的hash。这时,消息的接收者依然没有办法发现对源密文的篡改。所以依靠单向散列函数计算hash值仍然不能解决消息完整性校验的问题。
MAC
MAC(Message Authentication Code, 消息验证码),MAC是一种与密钥相关的单向散列函数,从而解决了普通hash的问题。密文的收发双方需要提前共享一个密钥,密文的发送者将密文的MAC值跟随密文一起发送,密文的接收者通过共享密钥计算收到的密文的MAC值,与附加的MAC值进行对比,就可以对收到的密文进行完整性校验。篡改者因为没有共享密钥,所以降低了被篡改的概率。
在Mode需要初始向量,或nonce, 或counter等情况下,需要将其作为附加消息一起计算MAC值。
GMAC
GMAC(Galois MAC) 是利用GF(Galois) 域的乘法运算来计算消息的MAC值的
GCM
以下为GCM模式。其中Ek为加密算法,AES-GCM中即为AES加密算法。Mh是将输入和密钥h在有限域GF上做乘法。
GCM即是将CTR计数器模式,和GMAC验证码结合。这样即可以并行地进行加密过程,又可以对信息的完整性进行校验。
同时因为计数器模式属于流加密模式,而非块加密模式。因此GCM中明文不需要进行padding。
实践
JMH, Java Microbenchmark Harness, 是专门用于进行代码的微基准的一套测试API。在AES-GCM和AES-CBC的benchmark中,使用了JMH来进行性能的测试。
Demo
加密一般是作为工具类存在的。因此在encrypt和decrypt方法中,进行了Cipher实例的创建。代码中抽出创建Cipher实例的部分,同时比较了一下两种模式Cipher实例创建的时间。实际进行工具类code撰写的时候,应进行优化。AES:AES加密解密工具类,提供CBC模式和GCM模式
MyTest: 使用JMH来做benchmark
Benchmark
Test Result 1
测试数据:长度32的字符串。
测试方法:一轮迭代时长为5s,每个需要测试的函数预热三轮,迭代15轮计算每次操作的平均耗时。
Test Result 2
测试数据:1109字中文文章。
测试方法:同上。
Test Result 3
由于单独的CBC是不包括GMAC,GCM包含了GMAC的计算,所以比较CBC+GMAC和GCM是比较合理的方式,Test 1和 Test 2中没有在CBC中另外加上GMAC。另外算法的具体实现方式也是影响其表现的。以下是其他参考的benchmark数据。
OpenSSL 2017/04
AES CBC+HMAC and GCM performance benchmarks for Java JCE, Bouncy Castle, Commons Crypto and Clojure Buddy.
总结
从Test1和Test2可以看出,一般应用加密解密场景中CBC和GCM性能差距不大。从其他的benchmark数据来看,总体上GCM的效果是好于CBC的。
从算法和模式设计上来说,GCM的可并行加密且具有验证位,CBC因为padding可能会收到攻击。因此GCM的安全性更高。
因此Sonarsource推荐使用AES-GCM来替换AES-CBC还是很有道理的。