EVP_CIPHER_CTX加解密接口函数说明

本文详细介绍了OpenSSL中EVP_CIPHER_CTX的加密和解密过程,包括EVP_EncryptInit_ex、EVP_EncryptUpdate、EVP_EncryptFinal_ex等函数的使用。内容涵盖加密模式、填充规则以及PKCS7填充的处理。同时,讨论了加密解密时对数据块大小的要求和填充选项的设置方法。

源码文件:/evp/evp_enc.c

EVP_CIPHER_CTX ctx;

加密函数说明:

1、EVP_CIPHER_CTX_init(&ctx)

把ctx清0

2、EVP_EncryptInit_ex(&ctx, EVP_rc4(), NULL, key, NULL);

初始化ctx
2.1、ctx->encrypt = enc;设置ctx为加密或者解密
2.2、ctx->cipher = cipher;设置ctx的加密算法为EVP_rc4()
2.3、ctx->cipher_data = OPENSSL_malloc(ctx->cipher->ctx_size);开辟算法的私有数据空间,并赋值给ctx,这个私有数据一般是秘钥,在这里是EVP_RC4_KEY。
2.4、根据算法设置ctx的其它一些成员
ctx->key_len = cipher->key_len;
ctx->flags = 0;
2.5、ctx->cipher->init(ctx, key, iv, enc) 把key设置到2.3所分配的cipher_data中
2.6、最后设置一些ctx成员
ctx->buf_len = 0;
ctx->final_used = 0;
ctx->block_mask = ctx->cipher->block_size - 1; 加密块掩码用例检测要加密的数据是否是各算法块大小的整数倍

3、EVP_EncryptUpdate()

3.1、如果要加密的数据大小是算法块大小的整数倍执行如下的if分支。直接调用EVP算法加密函数进行加密

if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {

*outl = 0;

if (ctx->cipher->do_cipher(ctx, out, in, inl)) {

*outl = inl;

return 1;

} else {

*outl = 0;

return 0;

}

}

 

3.2、如果要加密的数据大小不是算法块大小的整数倍,先只加密块大小最大整数倍大小的数据。
i = inl & (bl - 1); //计算要加密的数据大小除以块大小的余数
inl -= i;//要加密的数据最后不够块大小的数据暂时不加密
if (inl > 0) {
if (!ctx->cipher->do_cipher(ctx, out, in, inl))
return 0;
*outl += inl;
}
3.3、memcpy(ctx->buf, &(in[inl]), i);把剩余不够块大小没加密完的数据存到ctx里面
ctx->buf_len = i; 没加密完的数据大小也记录下来,在EVP_EncryptFinal_ex会用
3.4、在执行完EVP_EncryptUpdate()后,outl大小分三种情况:
3.4.1、outl = 0   要加密的数据大小不足块大小
3.4.2、outl = inl 要加密的数据大小大于块大小且是块大小的整数倍,这种情况下outl就是要加密的数据长度
3.4.3、outl = inl - (inl % block_size) 要加密的数据大小大于块大小但不是块大小的整数倍,这种情况下outl是要加密的数据长度减去最后不够块大小的数据长度

4、EVP_EncryptFinal_ex(&ctx, zz + i, &j)

一般用来处理最后没加密完的数据,这里的zz是要加密的数据,i是EVP_EncryptUpdate()处理完后的outl。
4.1、bl = ctx->buf_len;这个长度是0或者是没加密的数据长度

4.2、如果bl不是0,并且加密模式被设置过EVP_CIPH_NO_PADDING,返回0(表示加密失败)。

 如果明文大小是块大小的整数倍,outl设置为0,就返回1(表示最后执行成功),这种情况下没必要再执行EVP_EncryptFinal_ex函数

if (ctx->flags & EVP_CIPH_NO_PADDING) {

if (bl) {

EVPerr(EVP_F_EVP_ENCRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);

return 0;

}

*outl = 0;

return 1;//EVP_EncryptFinal_ex()只对需要填充的模式起效

}

4.3、对最后的数据填充至算法block_size大小
n = b - bl; //b= ctx->cipher->block_size;算法的块大小。bl大小见4.1的说明
for (i = bl; i < b; i++)

ctx->buf[i] = n; 

buf[EVP_MAX_BLOCK_LENGTH]这里的buf是固定32字节的一个数组,

也就是说openssl只支持最多32字节的块大小,如需要支持更大块大小,需要修改代码。

可以看出openssl采用的是pkcs7的填充规则(如果有n个字节需要填充,那么每个字节就填入n,十六进制填充)。

4.4、对最后一个填充过的块进行加密

ret = ctx->cipher->do_cipher(ctx, out, ctx->buf, b);//加密成功后,密文的大小一定是算法块大小的整数倍。

 

 


解密函数说明:
5、EVP_DecryptInit_ex和EVP_EncryptInit_ex是一样的,都是掉用的EVP_CipherInit_ex()函数,EVP_CipherInit_ex的最后一个参数是1还是0来设置ctx是加密还是解密
6、EVP_DecryptUpdate

 

 

6.1、if (ctx->flags & EVP_CIPH_NO_PADDING)

 

return EVP_EncryptUpdate(ctx, out, outl, in, inl);

不填充,直接解密。这里加解密都是调用的EVP_EncryptUpdate(),具体是加密还是解密是根据ctx->encrypt的值来判断的,在2.1中ctx初始化时设置这个成员

这个时候执行EVP_EncryptUpdate()中如下代码:

if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {

if (ctx->cipher->do_cipher(ctx, out, in, inl)) {

//解密成功

*outl = inl;

return 1;

}

*outl = 0;

return 0;

}
不填充,密文长度是块大小的整数倍,解密才成功。如果填充,不执行这段代码。
6.2、EVP_EncryptUpdate(ctx, out, outl, in, inl)执行解密
6.2.1、如果要解密的数据大小是块大小的整数倍,执行6.1中执行的EVP_EncryptUpdate()代码

6.2.2、如果要解密的数据大小不是块大小的整数倍,执行EVP_EncryptUpdate(),这时调的是具体算法的解密函数。

和加密类似,解密时最后一个不够块大小的密文块不会被解密,存放到ctx->buf里面,同时设置ctx->buf_len = i,XXX_final函数会用到

正常情况下,密文是不会出现大小不是块大小的整数倍的。

 

6.3、处理最后一个密文块,也就是明文填充后加密出的那个密文块

if (b > 1 && !ctx->buf_len) {

//正常情况下会走这个分支,正常情况是指,密文是由相应的加密算法成功加密的结果,不是随意的一个字符串。

*outl -= b;//需要单独处理最后一个填充过的块,除最后一个填充过的块,其它数据块跟原来的明文是一样的。

ctx->final_used = 1;

memcpy(ctx->final, &out[*outl], b); //把解密后最后一个带填充值的数据块,复制到ctx->final_used里面

}

7、EVP_DecryptFinal,这个函数直接调用的EVP_DecryptFinal_ex(),并无其它操作

8、EVP_DecryptFinal_ex

8.1、如果是不填充的,并且还有没解密完的数据,报错,返回解密失败0。

如果是不填充的,并且密文长度是块大小的整数倍,什么都没做就返回了,这种情况下就没必要再调EVP_DecryptFinal了。

if (ctx->flags & EVP_CIPH_NO_PADDING) {

if (ctx->buf_len) { 

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH);

return 0;

}

*outl = 0;

return 1;

}

8.2、如何还有没解密完的数据块,直接返回,报错。密文不正确、不是块大小的整数倍才会出现没解密完的数据块。

if (ctx->buf_len || !ctx->final_used) {

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_WRONG_FINAL_BLOCK_LENGTH);

return (0);

}

 

8.3、从填充数据的最后一个字节获取填充的值,并检测获取到的值是否合法

n = ctx->final[b - 1];

if (n == 0 || n > (int)b) {

//填充的值是不能为0的,也不能大于算法的块大小

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT); 

return (0);

}

for (i = 0; i < n; i++) {

//循环检查填充的值是否正确,检测规则是:根据最后一个字节的值n确定最后的几个字节需要检查,并且每个字节的值等于n。

if (ctx->final[--b] != n) {

EVPerr(EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_BAD_DECRYPT);

return (0); 

}

}

8.4、剔除填充的值

//计算填充块中,明文的长度

n = ctx->cipher->block_size - n;

for (i = 0; i < n; i++)

out[i] = ctx->final[i];   //把明文放到out里面,这个out是EVP_DecryptUpdate()执行完后的out+outl1

*outl = n;

这样在应用程序调用EVP_DecryptFinal()这个函数后,这个outl的值就是最后一个填充过的数据块中原来明文的长度。这个outl(假设为outl2)和EVP_DecryptUpdate()处理完成后的outl(假设为outl1)合起来就是原来明文的长度,

其实完整的明文在调用EVP_DecryptUpdate()后已经全部解密出来了(只是需要确定最后一个填充块中明文的长度)

最后掉用EVP_DecryptFinal()用来确定填充块中的明文的长度。

EVP_DecryptUpdate()执行完成后的out就是解密后明文的起始地址,outl1+outl2就是解密后的明文长度

 

密文一定要是对应算法块大小的整数倍,在调用解密的EVP_XXX函数之前可以做个检查,不满足这个条件可以不用解密了
不填充,只能加密块大小整数倍的明文,否则,加密结果是不完整的

 

 

9、关于设置是否填充的接口

 

 

EVP_CIPHER_CTX_set_padding(ctx, flag)

flag是0表示没有padding,flag是1表示有padding。默认是有padding的结果

EVP加密步骤:
OpenSSL_add_all_ciphers();
EVP_get_cipherbyname(xxx);
EVP_CIPHER_CTX_init(&ctx);

EVP_EncryptInit_ex(&ctx, ec, NULL, key, NULL);

 

//padding为0表示不填充,1表示填充,这里会根据padding的值来设置ctx->flags的值。不调此函数跟padding是1的效果一样

EVP_CIPHER_CTX_set_padding(&ctx, padding);

EVP_EncryptUpdate(&ctx, out, &out_len, input, input_len); 

input是明文,input_len是明文长度;out是密文,out_len是密文长度

如果明文不是算法块大小的整数倍,这里的out_len是不包含最后一个填充块加密出的密文长度。
EVP_EncryptFinal_ex(&ctx, out + out_len, &out_len_final); //加密最后一个填充的数据块,out+out_len作用是确定加密后的密文放置位置。最后一个需要填充的明文块被放置在ctx->buf里面
本函数在填充后会调用算法的加密函数进行加密,并把加密出的密文放到out+out_len处。
如果明文大小是块大小的整数倍,可以不调本函数。这种情况下ctx->buf是空的,这个函数不会有下一步的加密操作
如果明文大小是块大小的整数倍,调用了本函数,并且是填充模式,那么就会填充整个块大小(也就是整个数据块都是填充的值,并不包含明文)的数据来进行加密,这其实是不需要的
从out开始的out_len+out_len_final长的字符就是完整的密文

加密完成后的密文不能直接在网络上发送。直接写文件也是乱码,也不合适。需要做BASE64转换后在发往网络或者写文件。以下是用lcrypto库提供的BASE64转换接口实现的BASE64转换。

int base64_encode(char *in_str, int in_len, char *out_str)
{
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    size_t size = 0;

    if (in_str == NULL || out_str == NULL)
        return -1;
    
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);
    
    BIO_write(bio, in_str, in_len);
    BIO_flush(bio);
    
    BIO_get_mem_ptr(bio, &bptr);
    memcpy(out_str, bptr->data, bptr->length);
    out_str[bptr->length] = '\0';
    size = bptr->length;
    
    BIO_free_all(bio);
    return size;
}       

int base64_decode(char *in_str, int in_len, char *out_str)
{   
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    int counts;
    int size = 0;
    
    if (in_str == NULL || out_str == NULL)
        return -1;
    
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    
    bio = BIO_new_mem_buf(in_str, in_len);
    bio = BIO_push(b64, bio);

    size = BIO_read(bio, out_str, in_len);
    out_str[size] = '\0';
 
    BIO_free_all(bio);
    return size;
}


EVP解密步骤
OpenSSL_add_all_ciphers();
EVP_get_cipherbyname(xxx);
EVP_CIPHER_CTX_init(&ctx)
EVP_DecryptInit_ex(&ctx, ec, NULL, key, NULL);

EVP_CIPHER_CTX_set_padding(&ctx, padding);

//input是密文,input_len是密文长度;out,是明文,out_len是明文长度

EVP_DecryptUpdate(&ctx, out, &out_len, input, input_len);  
如果密文是没填充过的,那么out_len就是完整明文的长度,否则是明文的部分长度
这一步里面明文已经完全解密出来放到out里面了,只是在有填充时,还不确定最后一个块里面有几个字节是明文EVP_DecryptFinal_ex(&ctx, out + out_len, &out_len_final);从最后一个包含填充值的明文块里计算剩余的明文长度,并把剩余的明文长度赋值给out_len_final

从out开始的out_len+out_len_final长的字符就是完整的明文

上面两个例子中的out空间要够存放加密或解密的值。这个空间大小在加密时,可设置成明文长度+算法的块大小;解密时,可设置成密文长度。

如果是不填充的
加密时,只能加密长度是块大小整数倍的明文,否则加密的结果是不完整的。
解密时,EVP_DecryptUpdate()执行后的out包含的字符就是解密后的内容,这个时候out_len始终是0,不能用来确定解密后的明文长度。不需要再调用EVP_DecryptFinal_ex(),调的话也是执行失败

如果是填充的
不管明文长度是否是块大小的整数倍,加解密都要调用EVP_EncryptFinal_ex()

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值