什么是AEAD

按照维基百科的说法。AEAD的全称是Authenticated encryption (AE) and authenticated encryption with associated data (AEAD, variant of AE)。也就是带附加数据的加密和验证算法。

我们很多涉及IO的系统收发数据的时候一般会加上一些校验码,以便检测IO错误。而对外的socket里,这个校验码还有一个功能是挡掉一些不正常的数据。如果这时候如果我们的数据需要带上加密的话,那就是AE了。然后AEAD就是在AE的基础上,增加一些自定义数据,用于防止猜解。

按维基百科的说法,AEAD也分为 Encrypt-then-MAC (EtM,先加密,再对密文做hash) 、Encrypt-and-MAC (E&M,先对原文做hash,然后分别加密原文和hash值) 和 MAC-then-Encrypt (MtE,先对原文hash,再把hash值附加到原文后,最后一起加密) 三种。

所以,其实我们有些服务可以直接用AEAD,而不是自己去加密然后算hash值,这样反而安全性更高一些。并且像openssl这类加密算法库还能使用一些硬件加速。

扩充crypto_cipher的加密套件

原来我为了写 atframework 的网络接入层,实现了对opensslmbedtls的适配和对接。也是为了同时满足服务器上高性能和客户端设备上易集成。既然已经有了,那么我就往这里面加入了AEAD的支持。区分加密套件是否是AEAD的类型,并且统一成一个易用的接口。openssl的接口实在太晦涩,而mbedtls的接口设计比openssl好太多,所以接口使用了mbedtls的形式。实际测下来,openssl的性能比mbedtls高一个数量级,所以服务器还是用openssl比较好。

原来的版本里,crypto_cipher 就已经支持常用的XXTEA、RC4、AES-128/192/256-CFB了。并且使用openssl 1.1以上的花,还支持chacha20。其中XXTEA是内置提供的加密算法代码,而其他的来自于加密库的cipher模块。既然扩充了,就顺便增加一下opensslmbedtls都支持的其他cipher。像是aes-128/192/256-ecb/cbc、des-ecb/cbc/ede/ede3、BLOWFISH、camellia等等。

AEAD算法比原来的接口多一个associated data的参数和一个tag的参数。所以我另外加了两个接口( encrypt_aeaddecrypt_aead )专门用于AEAD的加解密。同时这两个API在调用的时候也会检查用户是否使用了正确的接口,防止误用。

这里最重要的是保证数据和流程上标准化,也就是无论是用openssl还是mbedtls又或是其他库加密和解密的结果要一致。那怎么实现这个呢?当然最好的方法是单元测试和CI。

openssl的单元测试套件

之前crypto_cipher里的单元测试数据是mbedtls里爬出来的。但是我看到openssl里的单元测试数据集(evptests.txt)更完整,覆盖也更全面。所以就直接写了代码读它的测试数据集了。配合上原来的测试数据,然后CI里的构建矩阵同时开opensslmbedtls两种模式。这样所有支持的算法就都在测试集中了。

libsodium和openssl的chacha20

现在Google的chacha20算法比较火热,所以我们支持了chacha20chacha20-poly1305,一个仅仅是加密,另一个是AEAD。我没有实测过,但是看到一些文章说,在支持硬件加速的设备上,AES-128-GCM性能是ChaCha20-Poly1305的好几倍。而现代化的设备里已经越来越多的设备支持硬件加速AES算法了。

现在有一些开源框架支持使用libsodium来补充更完整的chacha20支持。libsodium 提供了更多的salsa20系列的算法,包括chacha20-ietf、xchacha20、chacha20-poly1305、chacha20-poly1305-ietf、xchacha20-poly1305-ietf。这样如果嵌入式设备和移动终端使用mbedtls的话,即便cipher里没有,也可以用这个库来提供 chacha20 的支持。

在单元测试的过程中我发现,在openssl里的名字为chacha20-poly1305的cipher,实际上对应的是 libsodium 里的chacha20-poly1305-ietf。所以导致这里不得不对openssl的名字也做一次名字转换。另外在 openssl 的代码和单元测试中,所有的 AEAD算法 都是支持动态iv长度、并且可以一定程度上设置associated data和tag的长度的。但是 libsodium 里这些长度都是固定的。这也是这两个库最大的不一致的地方。

最后

这次也是学习了解一下这个密码学的知识吧。之前确实是不了解AEAD,使用这个API和单元测试走了一遍坑之后也算是大致了解了这些加密的基本流程。像是openssl并没有 all in one的接口。如果想当然的用的话还是比较容易误用的,误用的结果就是可能和其他环境交互的时候出现各种不匹配的问题,也很难去查。现在封装好更易用的API并且先把坑淌一遍的话,以后在使用的时候就可以手到擒来了。