聊聊AES

说起加密,通常分为对称加密和非对称加密,所谓对称加密中的对称,指的是加密和解密使用的是同一个密钥,如此说来什么是非对称就不用我多做解释了。对称加密相对于非对称加密而言,优点是速度快,缺点是安全性相对低一点,不过只要能保证密钥不泄露,其安全性还是有保证的,所以在实际项目中,对称加密的使用非常广泛。

目前最流行的对称加密标准是 AES。需要说明的是:AES 是一个标准,而不是一个算法,实际上背后的算法是 Rijndael,二者很容易混淆,比如很多人会搞不清楚 AES256 和 Rijndael256 有什么不同,甚至会认为是一个东西。其实 AES256 中的 256 指的是密钥的长度是 256 位,而 Rijndael256 中的 256 指的是分组大小是 256 位,更进一步说明的话,因为 AES 的分组大小是固定的 128 位,所以我们可以认为 AES256 等同于密钥长度是 256 位的 Rijndael128,听着有点绕,推荐阅读「AES 简介」:

AES

AES

了解了 AES 密钥之后,再说一下填充的概念。引用「漫画解读:什么是AES算法」中的描述:在对明文加密的时候,并不是把整个明文一股脑加密成一整段密文,而是把明文拆分成一个个等长的明文块,这些明文块经过加密器的复杂处理,生成一个个独立的密文块,再把这些密文块拼接在一起,就是最终的加密结果。但是这里涉及到一个问题:假如一段明文长度是 192 位,如果按每128 位一个明文块来拆分的话,那么最后一个明文块只有 64 位,不足128 位,此时就需要填充,那为了补齐 128 位而额外填充的 64 位数据具体是什么内容呢,答案就是字节长度: 64 位换算成字节就是 8 字节,所以额外填充的 8 字节的具体内容都是 0x08。再说一个例子,如果明文长度是 128 位,按每 128 位一个明文块来拆分的话,恰好是一个完整的块,此时还需要填充么?答案是需要,仍然需要填充一个完整块的长度!为什么呢?因为加密前要填充,解密后要去掉填充,如果没有填充,假设解密后最后一个字节恰好是 0x01,那么不方便判断这个 0x01 是实际的数据还是之前填充的数据。实际使用中有很多填充标准,其中最常见的是 PKCS#5 和 PKCS#7,它们的主要区别在于块大小的定义上: PKCS#5 中的块特指长度是 64 位(也就是 8 字节),而 PKCS#7 中的块没有特指某个长度,一般是 128 位(也就是 16 字节),可以说 PKCS#5 是 PKCS#7 的一个特例。

了解了 AES 密钥和填充两个概念后,还需要了解一下模式的概念,不过鉴于实际使用 AES 的时候,多数时候采用的都是 CBC 模式,本文就不详细展开讨论此概念了,但是需要说明的是 CBC 模式中有一个 iv (初始化向量)的概念,乍一看上去它好像是另一个密钥,实际上它并不是 Key,可以把它理解成我们使用 md5 时的 salt,通过对不同的数据使用不同的 salt,可以避免遭遇彩虹表撞库之类的暴力破解,iv 的作用亦如此,重要的是保证其随机性,你可能担心如果 iv 是随机的,那么加密方不是要把 iv 传递给解密方才能正常解密么?是的,需要传递 iv,但这不是问题,切记 iv 不是 Key,可以被别人看到,重要的是保证其随机性,从而保证同一份数据多次加密得到的结果并不相同,更多说明参考:Why can’t the IV be predictable when its said it doesn’t need to be a secret?。

BTW:在腾讯微信公众平台加解密方案中,iv 使用的是 Key 的前 16 位,是一个固定值,从 iv 的本意来看,这并不是一个好的选择,因为它没有保证随机性。

下面我通过一个例子来加深一下大家学习的印象:OpenSSL 缺省会执行填充,那么它执行的是 PKCS#5 还是 PKCS#7 呢?我们不妨做个试验来验证一下:

shell> echo -n a \
    | openssl enc -e \
        -aes-256-cbc \
        -K 3132333435363738313233343536373831323334353637383132333435363738 \
        -iv 31323334353637383132333435363738 \
    | openssl enc -d \
        -aes-256-cbc \
        -K 3132333435363738313233343536373831323334353637383132333435363738 \
        -iv 31323334353637383132333435363738 \
        -nopad \
    | xxd

00000000: 610f 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f 0f0f  a...............

通过把数据填充加密后但是在解密的时候不去掉填充(nopad),这样数填充了多少个字节就能确定答案,如上明文数据是「a」(0x61),填充数据是 15 个 0x0f,所以我们可知块大小是 16 个字节(不是 8 个字节,所以不是 PKCS#5),所以是 PKCS#7。

最后需要说明的是,上面那些一大长串的东西是什么玩意?实际上这是因为 OpenSSL 在命令行上使用时,K 和 iv 传递的都是十六进制的字符串:

shell> echo -n 12345678123456781234567812345678 | xxd -p
3132333435363738313233343536373831323334353637383132333435363738

shell> echo -n 1234567812345678 | xxd -p
31323334353637383132333435363738

也就是说,真正的 K 是「12345678123456781234567812345678」,32 个字节,也就是 256 位;真正的 iv 是「1234567812345678」,16 个字节,也就是 128 位,均符合 AES256 的标准要求。怎么样,看完本文,你理解了 AES 没有?

文章来源:

Author:老王
link:https://blog.huoding.com/2019/05/06/739