对称加密算法之AES

DinS          Written on 2017/11/24

本文介绍对称加密算法之AES,以及AES之下的几种模式,确保阅读过《crypto++框架介绍》,特别是理解pipelining模式。

先来了解下对称加密的概念,一图流:

图片来源:https://baike.baidu.com/item/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86

简单来说,对称加密就是同一把密钥既用来加密又用来解密,所以叫对称。
优点:加密解密速度快,效率高。
缺点:如何保证密钥安全传递是个问题。另外一旦一方密钥泄露,加密就被破解了。

AES(Advanced Encryption Standard)是对称加密算法的一种,在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。AES已然成为对称密钥加密中最流行的算法之一,因此,对称加密算法中就介绍AES。

关于AES的相关概念见http://blog.csdn.net/vieri_32/article/details/48345023
具体的AES加密过程可见http://www.cnblogs.com/block2016/p/5596676.html
这里不展开了,只讲讲如何用Crypto++来进行加密解密。

实际上《crypto++框架介绍》中已经展示了AES,为什么这里要再次探讨?
其实AES还细分成各种加密模式,上面介绍的AES没有任何模式,这是为了展示Crypto++的基础用法同时不增加复杂性。
在实际使用AES的时候通常需要指定一个加密模式并选择一种填充策略(paddling scheme)。为什么有模式呢?AES属于分组密码,即每次加密的区块必须是一个16字节的block,对于超过16字节的数据,则把数据分成若干个16字节的block,对于如何处理这些block,产生了不同的模式。
另外还有一个小问题,即对于最后一个block,如果数据不够填充,则剩下的字节如处理的问题。这个就是填充策略。

常见的有加密模式有ECB、CBC、CFB、OFB、CTR等,为了增加破解难度有些模式还需要一个初始向量(IV)。对几种常见模式的概念性介绍见https://www.cnblogs.com/happyhippy/archive/2006/12/23/601353.html。
但是Crypto++提醒,如果项目仅仅靠加密来确保数据安全,那么这些可能是不够用的,需要使用CCM、GCM、EAX等高级方式来确保 Authenticated Encryption。

一、ECB

ECB应该算AES中最简单的模式,不需要IV,加密区必须是16字节的block。
另外密钥长度必须是16、24、32字节之一,这个对所有模式的AES都一样。

经过了之前的学习,相信读者已经对Crypto++的套路熟悉了,下面看一下用ECB来加密的代码。
先来看一下如何指定密钥。密钥通常是个字符串,在之前我们使用了unsigned char数组,但是这有个问题。使用字符数组,最后一位必须是\n,因此假如使用16字节密钥,我们能够显式指定的只有15个字符,这个与人类认知不太相符。其实把密钥理解为一堆二进制就可以了,计算机加密时也只是操作二进制,字符只是我们看到的表象。因此可以用如下方式指定一个16字节的密钥:

输出结果:

结果是一致的,这说明我们成功制作了一把16个字符的密钥。
另外那个SecByteBlock是什么呢?是Crypto++提供的安全内存block,也就是说在安全度上比字符数组可靠,之后我们都会使用这个东西来存储密钥。

大图点这里

都是似曾相识了,需要解释的应该就是那个填充方式。
这个是可选的,如果不填就是DEFAULT_PADDING方式,会根据选择模式不同采取不同填充模式。PKCS是什么呢?这是一个填充约定,从#5可以看出还有其他编号。具体还有哪些以及有什么不同这里不讲了,只要知道#5处理的是Password-Based Encryption Standard即可。所以如果用AES来加密密码,就用它好了。
还有那个StreamTransformationFilter,从名字看知道是一个filter,大概就够了,作用就是在加密解密时处理padding。

运行结果:

接下来是解密,基本就是逆过程:

大图点这里

运行结果:

成功!
另外padding还可以自己手动填充,但是就复杂了,可以参考https://www.cryptopp.com/wiki/Ecb_mode。

上面讲到了AES的加密区必须是16字节,但是我们使用的明文是string,那么如果string内容超过了16字节会如何呢?得益于pipelining模式和StreamTransformationFilter,程序会自动处理多个block的情况。比如我们把明文改成:

同样的代码运行结果:

从base64输出可以看出原来的明文被拆成了2个block,最终的结果是正确的,这是使用pipelining的又一个佐证。
最后由于加密解密过程会抛出异常,所以最好配合try-catch使用。

完整的AES-ECB模式代码:

(大图点这里)

二、CBC

CBC是ECB的升级版,最大的不同是ECB每个block都是独立加密的,而CBC则把不同block通过IV联系起来了,加大了破解难度。IV仅仅是一个随机数据,没有什么特殊的要求。
来看代码实现,注意生成密钥和IV的方式:

(大图点这里)

整体而言跟ECB没有什么太多不同,只不过把ECB_Mode换成CBC_Mode而已,然后多了对IV的处理。
注意这次使用了AutoSeededRandomPool来生成密钥和IV,RSA部分见到过这个东西,但是没有细讲,这里可以看出其基础用法,就是用来随机生成一块数据区。当然密钥和IV随机生成后也许需要考虑保存下来,不然丢了就麻烦了,除非是一次性加密解密。
运行结果:

三、GCM

下面来介绍高级AES模式。
考虑这样一个问题:A给B发送AES加密的信息,可是这个信息被截获了。截获者由于没有密钥破解不了信息,但是他可以篡改信息,这样B收到的信息就不能够保证真实性了,但是A、B双方均不知情。
这种情况并不罕见,称为中间人攻击(Man-in-the-MiddleAttack,MITM)。

这种攻击对密码学提出了新的挑战,为此出现了新概念:AE(authenticated encryption)和AEAD(authenticated encryption with associated data),即加密数据要做到保密性(confidentiality),完整性(integrity)以及身份认证(authenticity)。
由这种概念产生了若干加密模式:CCM,GCM,EAX等,为什么这里选择GCM呢?根据Crypto++的wiki,GCM的性能较好,也得到了NSIT的认证,具有权威性,所以就选这个了。

先来说一下认证加密(AE)的套路。
在加密时,准备明文、密钥,以及可选的头部。头部是明文不需要加密,但是受认证保护。
加密结束后得到密文和认证标签(MAC, message authentication code)。
在解密时,准备密文、密钥、认证标签和可选的头部。解密结束后要么得到明文,要么会因为认证标签不匹配密文或头部而失败。

在GCM的语境中,数据被分成两部分:ADATA和PDATA。
ADATA指附加信息(additional data),PDATA指明文(plain data)。
GCM保证ADATA的认证性,以及PDATA的保密性和认证性。ADATA和PDATA可以为空,不过两个不能都为空。

之所以能够认证,是因为使用了hash。Crypto++提供了AuthenticatedEncryptionFilter来做加密,隐藏掉模式算法和hashFilter,这样代码上更简洁。反之解密使用AuthenticatedDecryptionFilter,原理一样。

如果只有PDATA,那么GCM用起来跟其他AES模式差不多。
如果有了ADATA,那么差别会比较大,这是因为要把ADATA和PDATA压入两个不同的channel,而且先后顺序有实质性影响。如果读者感兴趣,可以自行查找AEAD的代码,见https://www.cryptopp.com/wiki/GCM_Mode。
这里只分析AE加密解密。

大图点这里

加密过程没什么特殊的,注意SetKeyWithIV的时候要指定IV大小。
Pipelining时filter不同,参数已经在注释中说明。
关于那个TAG_SIZE,GCM设计的时候意图是使用128bit的tag,但是也可以缩小tag大小,120, 112, 104, or 96 bit都可以,算都是算128bit,然后根据参数截断tag的多余部分。一般而言用128bit就好。

大图点这里

解密稍有不同,但是看注释应该不成问题。
如果把DecryptionFilter放到pipelinging里new,那么pipelining结束后也会销毁,我们就没有机会查看tag值了,所以单独拿出来,然后在pipelining里使用。Redirector借过来用,但是不销毁,最后查看tag值。
注意GCM解密时很可能抛出异常,所以必须要try-catch。
运行结果:

接下来模拟中间人攻击:

篡改数据的一位,运行结果:

这样确实能够发现MITM,证明GCM是有效的。

最后完整GCM模式代码:

(大图点这里)

对于其他加密算法,可见《crypto++框架介绍》文末。