AES算法
高级加密标准(Advanced Encryption Standard,AES)是美帝2001年发布的块加密算法,AES是属于块加密算法框架中的一个组件,所以理解AES的关键是搞清楚块加密算法。(块加密算法是一个“框”,AES只是“框”中的一个东西)
块加密算法工作原理
块加密算法也叫分组密码工作模式(block mode of operation)它会把明文按等长的块(Block)分组然后利用某种加密算法进行加密——AES就属于“某种加密算法”中的一种。用一幅图表示二者关系:
块加密算法有三个关键部分
- 填充方式,负责把明文切分成一块一块的。块加密要求数据一定要符合块大小,以AES为例它规定每个数据块的大小是128个bit位(16字节),如果数据不足16字节那么必须**填充**到16字节。填充的数据是字节长度,比如一个5字节的块需要填充到16个字节,那么剩下的9个字节就全部写上09 09 09……。 PKCS5Padding和PKCS7Padding是分别出在不同规范的两个标准,PKCS5规定了块大小是8字节;PKCS7没有限制。由于AES已经限制了块的大小,所以它们两个在AES里面其实没有什么区别。所以在Java里面只提供了AES+PKC5Padding(AES+PKCS7Padding,这个说法不对,AES已经限制了块大小)。
- 加密算法,负责对每一块的明文进行加密。对算法输入明文和密钥,算法输出加密后的密文块,常见的算法是AES、DES。
- 工作方式,块加密非常灵活利用不同的工作模式可以实现**并行、密文可变(每次加密得到的密文都不一样)、容错**
块加密算法的五种工作方式
按照块加密算法的不同工作模式常见的有5种(为了简化问题我只贴加密过程):
Key是密钥,Plaintext是明文,中间的Block Cipher Encryption是加密算法(比如AES就是其中一种)。密钥和明文作为输入经过加密之后得到密文——Ciphertext。
ECB工作模式非常简单,可以并行处理;一个线程负责把数据切分成N块后由N个线程同时进行加密。它的缺点是同样的密钥每次执行加密出来的数据都是相同的。正常人看——比如我,这太正常了,但是“密码专家”们认为这太弱鸡了(囧)。所以他们设计了一种特别的算法,通过一个叫“初始向量(IV,Initialization Vector)”的变量让每次进行加密得到的密文都不一样(即便密钥相同)。剩下的4种块工作模式都属于这种牛B的类型。
这里的输入多了一个叫Initialization Vector(IV)的变量;明文和IV异或之后通过作为加密算法的一个变量输入,密钥作为另一个变量输入。
CBC加密算法是一个串行算法,第二块的加密依赖于***块密文作为IV。所以计算它的时候只能按部就班一块一块的计算。
之前的块加密都没有解决容错问题——如果我一个数据块坏掉了那么能不能解密出其余的数据块。于是就有了CFB工作模式,注意观察上图,解密的时候如果***个密文块损坏那么可以无视这块内容,直接用第二个密文作为输入对第三个密文块进行解密。
CBC虽然狂屌炸(每次都能算出不同的密文)但是不能并行,对于“时间就是金钱”的计算机来说是无法容忍的。于是就有了***次改进——OFB。
注意IV和密钥经过加密后这里是可以并行的,其中一个线程用于和明文的异或;一个线程可以立马计算“下次”加密。
OFB算法的并行度太低,仅仅实现一部分并行,于是就有了第二次改进——CTR算法,同时保证了并行度和密文可变性。
CTR算法中的IV变成了两部分,***个是Nonce可以是一个随机序列,第二部分是计数器(Countter),是一个递增的数字。于是加密的时候通过组合Nonce和计算器就可以对得到有规律但是不相同的(每次密文都不同的关键是IV的可变)“IV”。
总结
从三个维度理解五种工作模式——密文是否固定、是否可以并行、有没有容错
- ECB密文固定,全并行
- CBC密文可变,不可并行
- CFB密文容错,密文可变,不可并行
- OFB密文可变,部分并行
- CTR密文可变,全并行
需要注意的是除非数据量特别大否则我们不必在乎是否并行;容错在小数据量的时候也凸显不出效果,所以CBC一般是***的选择。
如何跨语言
很多朋友都碰到一个语言写的AES加密在另个一语言解密不了的问题,究其原因是由于根本没有理解AES的工作模式(可能就是Google了一下AES加密,然后代码贴上收工)。所以我觉得跨语言的***步不是找到一种能在所有语言通用的工作模式——所有工作模式每个语言几乎都支持;而是搞清楚你加密出来的数据是那种工作模式,有没有用到IV?IV是通过什么方式传递给对方的?
比如下面的Java代码:
我使用了CBC加密模式,这种模式涉及到IV,我们可以用一个固定的IV(比如用key作为IV)——但是意味着没有了CBC的好处,密文可变。所以我用一个随机16字节作为IV,返回的时候把它作为***个数据块;解密的时候只要取出***个数据块作为IV,然后再对余下的数据进行解密。
如果我们不指定IV参数(init函数的第三个参数),那么iv就是一个随机数。Java是不会主动把IV附加到密文上,所以这种加密出来的数据是谁也没有办法解出来的。
【本文是51CTO专栏作者“邢森”的原创文章,转载请联系作者本人获取授权】