EVP实现AES加解密:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
int main(void)
{
unsigned char userkey[EVP_MAX_KEY_LENGTH];
unsigned char iv[EVP_MAX_IV_LENGTH];
// unsigned char *date = (unsigned char *)malloc(AES_BLOCK_SIZE*3);
int dateLen = 35; //******* [1] *******
unsigned char *date = (unsigned char *)malloc(dateLen);
unsigned char *encrypt = (unsigned char *)malloc(AES_BLOCK_SIZE*6);
unsigned char *plain = (unsigned char *)malloc(AES_BLOCK_SIZE*6);
EVP_CIPHER_CTX ctx; //******* [6] *******
int ret;
int tlen = 0;
int mlen = 0;
int flen = 0;
memset((void*)userkey, 'k', EVP_MAX_KEY_LENGTH);
memset((void*)iv, 'i', EVP_MAX_IV_LENGTH);
memset((void*)date, 'p', dateLen);
memset((void*)encrypt, 'H', AES_BLOCK_SIZE*6);
memset((void*)plain, 0, AES_BLOCK_SIZE*6);
/*初始化ctx*/
EVP_CIPHER_CTX_init(&ctx); //******* [7] *******
/*指定加密算法及key和iv(此处IV没有用)*/
ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, userkey, iv);
if(ret != 1) {
printf("EVP_EncryptInit_ex failed\n");
exit(-1);
}
/*禁用padding功能*/
EVP_CIPHER_CTX_set_padding(&ctx, 0); //******* [2] *******
/*进行加密操作*/
ret = EVP_EncryptUpdate(&ctx, encrypt, &mlen, date, dateLen); //******* [3] *******
if(ret != 1) {
printf("EVP_EncryptUpdate failed\n");
exit(-1);
}
/*结束加密操作*/
ret = EVP_EncryptFinal_ex(&ctx, encrypt+mlen, &flen); //******* [4] *******
if(ret != 1) {
printf("EVP_EncryptFinal_ex failed\n");
exit(-1);
}
tlen = mlen + flen; //******* [5] *******
tlen = 0;
mlen = 0;
flen = 0;
EVP_CIPHER_CTX_cleanup(&ctx);
EVP_CIPHER_CTX_init(&ctx); //******* [8] *******
ret = EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, userkey, iv);
if(ret != 1) {
printf("EVP_DecryptInit_ex failed\n");
exit(-1);
}
EVP_CIPHER_CTX_set_padding(&ctx, 0);
ret = EVP_DecryptUpdate(&ctx, plain, &mlen, encrypt, AES_BLOCK_SIZE*3);
if(ret != 1) {
printf("EVP_DecryptUpdate failed\n");
exit(-1);
}
ret = EVP_DecryptFinal_ex(&ctx, plain+mlen, &flen);
if(ret != 1) {
printf("EVP_DecryptFinal_ex failed\n");
exit(-1);
}
/*对比解密后与原数据是否一致*/
if(!memcmp(plain, date, dateLen)) {
printf("test success\n");
} else {
printf("test failed\n");
}
printf("encrypt: ");
int i;
for(i = 0; i < AES_BLOCK_SIZE*3+4; i ++){
printf("%.2x ", encrypt[i]);
if((i+1)%32 == 0){
printf("\n");
}
}
printf("\n");
for(i = 0; i < dateLen; i ++){
printf("%.2x ", date[i]);
if((i+1)%32 == 0){
printf("\n");
}
}
printf("\n");
for(i = 0; i < AES_BLOCK_SIZE*3; i ++){
printf("%.2x ", plain[i]);
if((i+1)%32 == 0){
printf("\n");
}
}
printf("\n");
#endif
return 0;
}
本文中有一个很不明白的点,共有三个len变量,分别是tlen,mlen,flen,分别是什么作用
基于这个问题,在[1]处,重新指定了源数据长度为35,
- debug发现:
- 当[2]处设置禁用padding时:
在[3]处,mlen值为32;
在[4]处,flen值为0,并且EVP_EncryptFinal_ex返回0,即失败。 - 当[2]处注释,则:
[3] mlen值为32;
[4] flen值为16;
[5] tlen值为48。
- 综上所述,结合Openssl Manpage
If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the “final” data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below. The encrypted final data is written to out which should have sufficient space for one cipher block. The number of bytes written is placed in outl. After this function is called the encryption operation is finished and no further calls to EVP_EncryptUpdate() should be made.
If padding is disabled then EVP_EncryptFinal_ex() will not encrypt any more data and it will return an error if any data remains in a partial block: that is if the total data length is not a multiple of the block size.
可得:若禁用padding,则需要确认加密源数据为16字节的整数倍,否则最后生于不足16字节的数据将不加密;如果使用padding,则末尾不足16字节,openssl将做补齐。
2019/4/11 更:
问题一
- 现象: [6] 处提示"不完整的类型",
- 解决方法:here
问题二
- 现象:将项目移植到Windows下时,编译通过,运行莫名其妙中断,debug发现在 [6] 处发生内存访问异常。
- 解决方法:查阅资料得知 EVP_CIPHER_CTX_init() 接口用于对ctx清零,注释掉 [7],执行
memset(&ctx, 0, sizeof(EVP_CIPHER_CTX));
,o**k,成功。
不过有一点很奇怪,在 [8] 处同样调用了 EVP_CIPHER_CTX_init() 接口,但是没报错,还没找到原因。