描述
EVP接口是一个高级别的对称加密接口,该接口封装了所有对称加密的算法。
以下内容来自openssl1.0.2:/docs/man1.0.2/man3/EVP_aes_256_cbc_hmac_sha1.html
AES加密
AES是基于块的对称加密算法,基于块的加密算法又称cbc加密,所以AES也叫做AES_CBC。
根据其密码长度128,192,256,则类型进一步扩展为AES_128_CBC,AES_192_CBC,AES_256_CBC。
在AES基础上又可以扩展出增加2种模式:GCM和CCM,这2中模式都是增加了额外的认证内容,实际使用中完全可以不用。
所以简单的使用,应该是,AES,128位密码,cbc,不启用GCM或者CCM,则:AES_128_CBC。
说明:基于块的加密,则加密原文必须是块大小,小于块的原文是不能直接加密的,所以需要填充。
过期接口
这些接口是过期的,不建议使用:
EVP_EncryptInit(), EVP_EncryptFinal(), EVP_DecryptInit(), EVP_CipherInit() and EVP_CipherFinal()
最新的加密建议使用下面的接口:
EVP_EncryptInit_ex(), EVP_EncryptFinal_ex(), EVP_DecryptInit_ex(), EVP_DecryptFinal_ex(), EVP_CipherInit_ex() and EVP_CipherFinal_ex()
aes加密相关函数
EVP_CIPHER_CTX_init()
初始化加密套件的上下文环境,必须第一个调用。
EVP_CipherInit_ex(), EVP_CipherUpdate() and EVP_CipherFinal_ex()
这组函数可以用来加解密,比起纯粹的加密、解密函数,这组更好用,所以加解密采用这组函数。其他纯粹的只加密、只解密函数就不再关注了。
EVP_CIPHER_CTX_cleanup()
清空加密上下文环境。
块大小:
AES的固定加密块大小是128bit。
明文和输出密文大小对应关系:
加密前块大小=加密后块大小。
明文和密文大小对于关系:
明文可能需要填充,则明文加密后的长度最大值是明文加上一个填充块:
密文长度 <= 明文长度 + 一个填充块长度
加密流程
加密的流程:
1.调用EVP_CIPHER_CTX_init创建上下文;
2.调用EVP_CipherInit_ex,设置上下文参数,例如密码等,该函数可以调用多次,设置完就可以加密了;
3.设置完参数就可以加密了,加密用到EVP_CipherUpdate()、EVP_CipherFinal_ex();
4.加密完成后,清空上下文,必须调用EVP_CIPHER_CTX_cleanup() ;
对于启用了填充的加密模式(启用填充,是默认模式);
加密说明:
1.对于启用了填充的加密模式,EVP_CipherFinal_ex()是用来加密最终的需要填充的数据的,如果最后没有多余的源数据剩下,则该函数也必须调用,且会全部填充一个数据块,并加密,同时清理一些东西及收尾。
2.对于没有启用填充的加密模式,调用EVP_CipherFinal_ex()时,如果加密上下文中保存有未加密的原文(一般是因为原文不是加密块整数倍导致有原文剩余)则会出错,否则什么也不会做(同样,可能有清理、收尾的动作,在老版本中应该是这样)。所以未启用填充模式的,自己要保存原文是加密块的整数倍,这个函数还是要调,但必须保证buff无剩余的原数据。
3.EVP_CipherFinal_ex调用后,标志加密结束了,开始销毁流程了。就不要在调用EVP_CipherUpdate()了。
4.EVP_CipherUpdate可以反复调用,用来加密数据。
解密说明:
1.同加密一样,EVP_CipherUpdate()进行解密数据,完成后调用EVP_CipherFinal_ex()收尾;
2.在启用了填充的模式下EVP_CipherFinal_ex()还会吐出数据,如果原文非块大小的整数倍;
3.在未启用填充模式下,EVP_CipherFinal_ex()也要调用,进行收尾;
cbc模式说明:
1.因为是cbc模式,所以加密必须是块大小才能加密,否则就出错,进一步的区别就是这个填充是自己做还是让openssl做;
2.解密也同理,必须是块大小,否则解密在最后一步EVP_CipherFinal_ex()就会校验出错;
3.在启用填充模式下,EVP_CipherFinal_ex()负责解密最后的填充块(及EVP_CipherUpdate()不解密填充块)同时校验最后的填充块格式,返回校验结果和解密数据;
4.在未启用填充块的模式下,调用EVP_CipherFinal_ex()时,加密上行文中应该没有任何缓存的带解密数据,否则就出错;
加解密流程图:
openssl中的一个例子:
int do_crypt(FILE *in, FILE *out, int do_encrypt)
{
/* Allow enough space in output buffer for additional block */
unsigned char inbuf[1024], outbuf[1024 + EVP_MAX_BLOCK_LENGTH];
int inlen, outlen;
EVP_CIPHER_CTX ctx;
/* Bogus key and IV: we'd normally set these from
* another source.
*/
unsigned char key[] = "0123456789abcdeF";
unsigned char iv[] = "1234567887654321";
/* Don't set key or IV right away; we want to check lengths */
EVP_CIPHER_CTX_init(&ctx);
EVP_CipherInit_ex(&ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
do_encrypt);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(&ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(&ctx) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(&ctx, NULL, NULL, key, iv, do_encrypt);
for(;;)
{
inlen = fread(inbuf, 1, 1024, in);
if(inlen <= 0) break;
if(!EVP_CipherUpdate(&ctx, outbuf, &outlen, inbuf, inlen))
{
/* Error */
EVP_CIPHER_CTX_cleanup(&ctx);
return 0;
}
fwrite(outbuf, 1, outlen, out);
}
if(!EVP_CipherFinal_ex(&ctx, outbuf, &outlen))
{
/* Error */
EVP_CIPHER_CTX_cleanup(&ctx);
return 0;
}
fwrite(outbuf, 1, outlen, out);
EVP_CIPHER_CTX_cleanup(&ctx);
return 1;
}
支持的所有对称加密列表(cipher list)
支持的每种加密类别,都可以通过函数获取其类型,不同的加密类型,通过不同的函数获取,这里我们只关注AES类型的加密类型获取函数。
EVP_aes_128_cbc()
密码长度128位的aes加密算法。
GCM和CCM
GCM和CCM是2种加密模式,这2种加密模式加密时需要一些额外的信息,这里不关注,忽略。
说明
加密有启用填充和不启用填充两种模式,默认是启用填充的。
如果启用填充,当加密数据总长度不是加密块的倍数时,缺失的数据会用填充填补,如果是整数倍,则会额外增加一个填充块
当启用填充后看,解密是否成功,可以在解密最后一个数据块时根据解密函数的返回结果判断出来,但是这个也不是100%准确的,错误数据仍然有1/256的概率被识别成成功解密,这个要注意。
如果不启用填充,当源数据长度是加密块的倍数时,解密总是返回成功。
结论:解密操作发生失败,则肯定是出错了,成功则未必表示数据已经解密成功。
对称算法主要有四种加密模式
(1) 电子密码本模式 Electronic Code Book(ECB)
这种模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
其缺点是:电子编码薄模式用一个密钥加密消息的所有块,如果原消息中重复明文块,则加密消息中的相应密文块也会重复,因此,电子编码薄模式适于加密小消息。
(2)加密块链模式 Cipher Block Chaining(CBC)
CBC模式的加密首先也是将明文分成固定长度的块,然后将前面一个加密块输出的密文与下一个要加密的明文块进行异或操作,将计算结果再用密钥进行加密得到密文。第一明文块加密的时候,因为前面没有加密的密文,所以需要一个初始化向量。跟ECB方式不一样,通过连接关系,使得密文跟明文不再是一一对应的关系,破解起来更困难,而且克服了只要简单调换密文块可能达到目的的攻击。
(3)加密反馈模式 Cipher Feedback Mode(CFB)
面向字符的应用程序的加密要使用流加密法,可以使用加密反馈模式。在此模式下,数据用更小的单元加密,如可以是8位,这个长度小于定义的块长(通常是64位)。其加密步骤是:
a) 使用64位的初始化向量。初始化向量放在移位寄存器中,在第一步加密,产生相应的64位初始化密文;
b) 始化向量最左边的8位与明文前8位进行异或运算,产生密文第一部分(假设为c),然后将c传输到接收方;
c) 向量的位(即初始化向量所在的移位寄存器内容)左移8位,使移位寄存器最右边的8位为不可预测的数据,在其中填入c的内容;
d) 第1-3步,直到加密所有的明文单元。
解密过程相反
(4)输出反馈模式 Output Feedback Mode(OFB)
输出反馈模式与CFB相似,惟一差别是,CFB中密文填入加密过程下一阶段,而在OFB中,初始化向量加密过程的输入填入加密过程下一阶段。
测试代码:
#include <openssl/evp.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define data_base_len 16 * 1000 * 10
#define process_times 1000 * 10
#define debug 0
#define run_test_1 0
#define run_test_2 1
/*
编译: g++ encryption.cpp -o my_program -lssl -lcrypto
time ./my_program
real 0m3.590s
user 0m3.541s
sys 0m0.001s
real:表示的是墙上时间,说白了,其实就是从程序运行开始到结束所经历的时间;
user:表示程序运行期间,cpu 在用户态所花费的时间;
sys:表示程序运行期间,cpu 在内核态所花费的时间;
*/
int do_crypt_1(unsigned char inbuf[], int inbuf_len,
unsigned char outbuf[], int outbuf_len,
unsigned char key[],
unsigned char iv[],
int do_encrypt)
{
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init(&ctx);
EVP_CipherInit_ex(&ctx, EVP_aes_128_cbc(), NULL, NULL, NULL, do_encrypt);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(&ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(&ctx) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(&ctx, NULL, NULL, key, iv, do_encrypt);
if(!EVP_CipherUpdate(&ctx, outbuf, &outbuf_len, inbuf, inbuf_len))
{
/* Error */
EVP_CIPHER_CTX_cleanup(&ctx);
printf("Error in EVP_CipherUpdate do_encrypt:%u\n", do_encrypt);
return 0;
}
int rr = EVP_CipherFinal_ex(&ctx, outbuf + outbuf_len, &outbuf_len);
// printf("Error in EVP_CipherFinal_ex do_encrypt:%u rr:%d\n", do_encrypt, rr);
EVP_CIPHER_CTX_cleanup(&ctx);
return 1;
}
int test_1()
{
unsigned char inbuf[data_base_len], outbuf[data_base_len + EVP_MAX_BLOCK_LENGTH], decryption_buff[data_base_len + EVP_MAX_BLOCK_LENGTH + EVP_MAX_BLOCK_LENGTH];
memset(inbuf, 'c', data_base_len);
inbuf[data_base_len - 1] = 0;
memset(outbuf, 0, data_base_len + EVP_MAX_BLOCK_LENGTH);
memset(decryption_buff, 0, data_base_len + EVP_MAX_BLOCK_LENGTH + EVP_MAX_BLOCK_LENGTH);
unsigned char key[] = "0123456789abcdeF";
unsigned char iv[] = "1234567887654321";
int32_t times = process_times;
while (times-- > 0) {
do_crypt_1(inbuf, data_base_len, outbuf, data_base_len + EVP_MAX_BLOCK_LENGTH, key, iv, 1);
do_crypt_1(outbuf, data_base_len + EVP_MAX_BLOCK_LENGTH, decryption_buff, data_base_len + EVP_MAX_BLOCK_LENGTH + EVP_MAX_BLOCK_LENGTH, key, iv, 0);
if (debug) printf("do_crypt_1[%u->%zu]:\n%s\n", data_base_len - 1, strlen((const char *)decryption_buff), decryption_buff);
}
}
int test_2()
{
unsigned char inbuf[data_base_len], outbuf[data_base_len + EVP_MAX_BLOCK_LENGTH], decryption_buff[data_base_len + EVP_MAX_BLOCK_LENGTH + EVP_MAX_BLOCK_LENGTH];
memset(inbuf, 'c', data_base_len);
inbuf[data_base_len - 1] = 0;
memset(outbuf, 0, data_base_len + EVP_MAX_BLOCK_LENGTH);
memset(decryption_buff, 0, data_base_len + EVP_MAX_BLOCK_LENGTH + EVP_MAX_BLOCK_LENGTH);
unsigned char key[] = "0123456789abcdeF";
unsigned char iv[] = "1234567887654321";
EVP_CIPHER_CTX ctx1;
EVP_CIPHER_CTX_init(&ctx1);
EVP_CipherInit_ex(&ctx1, EVP_aes_128_cbc(), NULL, NULL, NULL, 1);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(&ctx1) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(&ctx1) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(&ctx1, NULL, NULL, key, iv, 1);
EVP_CIPHER_CTX ctx2;
EVP_CIPHER_CTX_init(&ctx2);
EVP_CipherInit_ex(&ctx2, EVP_aes_128_cbc(), NULL, NULL, NULL, 0);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(&ctx2) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(&ctx2) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(&ctx2, NULL, NULL, key, iv, 0);
int times = process_times;
while (times-- > 0) {
int outbuf_len = 0;
if(!EVP_CipherUpdate(&ctx1, outbuf, &outbuf_len, inbuf, data_base_len))
{
/* Error */
EVP_CIPHER_CTX_cleanup(&ctx1);
printf("err!\n");
return 0;
}
int decryption_buff_len = 0;
if(!EVP_CipherUpdate(&ctx2, decryption_buff, &decryption_buff_len, outbuf, outbuf_len))
{
/* Error */
EVP_CIPHER_CTX_cleanup(&ctx2);
printf("err!\n");
return 0;
}
if (debug) printf("do_crypt_2[%u->%zu]:\n%s\n", data_base_len - 1, strlen((const char *)decryption_buff), decryption_buff);
}
int outbuf_len;
EVP_CipherFinal_ex(&ctx1, inbuf, &outbuf_len);
EVP_CipherFinal_ex(&ctx2, inbuf, &outbuf_len);
EVP_CIPHER_CTX_cleanup(&ctx1);
EVP_CIPHER_CTX_cleanup(&ctx2);
}
int main(int argc, char *argv[])
{
if (run_test_1) test_1();
if (run_test_2) test_2();
return 0;
}